91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page iii
RibbonX Customizing the Office 2007 Ribbon
Robert Martin Ken...
212 downloads
1417 Views
8MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page iii
RibbonX Customizing the Office 2007 Ribbon
Robert Martin Ken Puls Teresa Hennig
Wiley Publishing, Inc.
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page ii
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page i
RibbonX
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page ii
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page iii
RibbonX Customizing the Office 2007 Ribbon
Robert Martin Ken Puls Teresa Hennig
Wiley Publishing, Inc.
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page iv
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page v
RibbonX: Customizing the Office 2007 Ribbon Published by Wiley Publishing, Inc. 10475 Crosspoint Boulevard Indianapolis, IN 46256 www.wiley.com
Copyright © 2008 by Wiley Publishing, Inc., Indianapolis, Indiana Published simultaneously in Canada ISBN: 978-0-470-19111-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. For general information on our other products and services or to obtain technical support, 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. Library of Congress Cataloging-in-Publication Data is available from the publisher. Trademarks: Wiley and the Wiley logo 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. 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. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books.
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page vi
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page vii
To all of those who made this possible. — Robert Martin
For my daughter, Annika. I’m sure you’ll have this mastered by next year. — Ken Puls
I dedicate my work to the very special people in my life. I am blessed to have such wonderful family and friends with whom I share my adventures, excitement, and joy of new challenges. Life is full of opportunities; we should celebrate them all. I also dedicate this book to everyone who is adventuring into the world of Ribbon customizations. I am privileged to help you on your journey. — Teresa Hennig
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page viii
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page ix
About the Authors
Robert Martin holds a postgraduate degree in finance and economics from the University of London. He has worked as IT Director for FairCourt Capital in the U.K., and has done pro bono work with some African charities, such as NIDOE (Nigerians in Diaspora Organisation Europe and Africa 2000). He is the author of Excel and VBA in Financial Modeling: A Practical Approach, and has authored many eBooks on MS Office development. He currently works as an independent IT consultant. Robert has also run one of the most popular Excel forums in Brazil since 2004, which led to his MVP award in 2006 for helping develop the Microsoft Office community in Brazil. He is also a moderator in the Microsoft Technet forums in Brazil. Ken Puls has worked at Fairwinds Community & Resort on Vancouver Island, British Columbia, for over eight years. In his dual role as an accountant and the only IT support person on staff, Ken was exposed to a vast number of business systems, including point-of-sale systems, databases, and analysis tools. During his tenure with Fairwinds, Ken has led the installation of over a dozen systems conversions of various types, and become addicted to Microsoft Excel. Ken currently holds the position of controller, and continues to focus his efforts in supporting Fairwinds’ property development, golfing, and sailing lifestyle. More about Fairwinds can be discovered at www.fairwinds.ca. Ken has also worked as a freelance Microsoft Office developer, mainly in Excel, but also with Access, Word, and Outlook. In addition, he has taught several Excel training courses in his local area. He has been an active participant in many Web forums since 2002, and in recognition of his contributions to the online community was awarded the prestigious Microsoft Most Valuable Professional award in October 2006. He hosts a website at www.excelguru.ca, which provides code samples for working with Excel and other Microsoft Office applications; as well as a blog at www.excelguru.ca/blog. The blog holds much of his exploratory work with the Ribbon, including customizing it. Ken has held the Certified Management Accountant (CMA) designation since 2000.
ix
91118ffirs.qxd:WileyRedTight
x
12/2/07
3:11 PM
Page x
About the Authors Teresa Hennig loves challenges, solving problems, and making things happen. Her company, Data Dynamics NW, reflects her dynamic personality and her innate ability to quickly grasp a situation and formulate a solution. With a strong background in both the private and public sector, covering a wide spectrum of industries, Teresa provides both consulting and database development to optimize the processing, integration, and utilization of data. Her emphasis is on designing cost-effective solutions tailored to meet current and projected needs. In recognition of her expertise and dedication to the Access community, Teresa has again been awarded Microsoft Access MVP (Most Valuable Professional) status. She continues to serve as president of both the Pacific Northwest Access Developer Groups (PNWADG) and the Seattle Access Group. Since 2005, Teresa has served on several of INETA’s national committees to provide service, information, and support to user groups around the world. Her extensive writing experience includes being the lead author for the Access VBA Programmer’s Reference series and contributing to several other books. She also publishes two monthly Access newsletters and two Access websites, www.DataDynamicsNW.com and www.SeattleAccess.org. Teresa is passionate about helping and empowering others, both professionally and personally. One of her most notable charitable activities was developing a paperless patient tracking database for a teen clinic in Uganda and then spending nine days at the clinic to deploy the system and train the staff. She also climbed to the summit of Mt. Rainier, raising $5,000 for the American Lung Association. In addition, in honor of her brother, she rode 220 miles on a bike to raise $10,000 for the Spinal Cord Society’s research to cure paralysis. Oliver Stohr was born in Germany, but moved permanently to the United States in 2003, where he currently lives with his wonderful wife, Bonnie, and their cat and minidachshund, in Maryland. At present, he is a full-time student at the University of Maryland working toward his undergraduate degree in computer science. Oliver has been working with computers from a very young age and started using Microsoft products with the release of Windows 3.1. He has provided service to online discussion forums, answering more than 25,000 technical questions. His involvement has earned him a moderator status at UtterAccess forums as well as a prestigious Microsoft Most Valuable Professional award. Oliver was also the technical editor for Expert Access 2007 Programming and is the owner and webmaster of www.access-freak.com. Since it was launched, the site has helped thousands of users of Microsoft Access make the transition from earlier versions to the new 2007 edition.
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page xi
Credits
Executive Editor Robert Elliott Senior Development Editor Tom Dinse Technical Editors Jeff Boyce Nick Hodge Production Editor William A. Barton Copy Editor Luann Rouff Editorial Manager Mary Beth Wakefield Production Manager Tim Tate
Vice President and Executive Publisher Joseph B. Wikert Project Coordinator, Cover Lynsey Stanford
Compositors Craig Woods and Craig Thomas, Happenstance Type-O-Rama Proofreaders Jennifer Larsen, Amy Watson, Word One Indexer Robert Swanson Anniversary Logo Design Richard Pacifico
Vice President and Executive Group Publisher Richard Swadley
xi
91118ffirs.qxd:WileyRedTight
12/2/07
3:11 PM
Page xii
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xiii
Contents
Introduction
xxix
Part I
The Building Blocks for a Successful Customization
1
Chapter 1
An Introduction to the Office User Interface
3
Chapter 2
What Is the Ribbon and Why Does It Exist? Problems with the Old UI Issues Solved with the New UI Issues Created with the New UI What Happened to the Toolbars from My Pre-2007 Files? A Customization Example for Pre-2007 UIs Ribbon Components Tips for Navigating the Ribbon and Quick Access Toolbar (QAT) Using Keyboard Shortcuts and Keytips Using the Mouse Wheel Minimizing and Maximizing the Ribbon Adding Commands to the QAT Assigning a Macro to a QAT Button Changing the QAT Location Preparing for Ribbon Customization Showing the Developer Tab Showing CustomUI Errors at Load Time Reviewing Office 2007 Security Has Customization of the Office UI Become Easier? Conclusion
3 4 6 7 9 10 16 17 17 19 19 20 22 24 24 24 26 26 27 27
Accessing the UI Customization Layer
29
Accessing the Excel and Word Ribbon Customization Layers What’s New in Excel and Word Files? Creating a Ribbon Customization with Notepad
30 30 30
xiii
91118ftoc.qxd:WileyRedTight
xiv
12/3/07
9:54 AM
Page xiv
Contents
Chapter 3
Creating the customUI File Creating the File to Use the Customized UI Attaching the XML to the File Using the Microsoft Office 2007 Custom UI Editor to Modify Your UI Installing Microsoft .NET Framework 2.0 for Windows XP Users Installing the Microsoft Office 2007 Custom UI Editor Using the CustomUI Editor to Customize the Ribbon Storing Customization Templates in the CustomUI Editor Some Notes About Using the CustomUI Editor XML Notepad Installing XML Notepad Using XML Notepad The Benefits of XML Notepad The Drawbacks of XML Notepad A Final Word on Excel and Word Customizations Microsoft Access Customizations Storing the CustomUI Information in Tables Creating an Access UI Modification Using a Table Access USysRibbons Caveat Other Techniques for Access UI Customizations Conclusion
30 31 32
36 38 39 41 42 43 43 43 47 48 48 48 49 49 51 52 52
Understanding XML
55
What Is XML and Why Do You Need It? Essential Background Tags Elements Attributes The id Attribute The label Attribute Tips for Laying Out XML Code Creating Comments in XML Code The Core XML Framework The customUI Element Required Attributes of the customUI Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the customUI Element The ribbon Element Required Attributes of the ribbon Element Optional Static Attributes Allowed Children Objects of the ribbon Element Graphical View of ribbon Attributes The tabs Element Required Attributes of the tabs Element Allowed Children Objects of the tabs Element
55 57 57 59 59 60 61 61 63 65 65 66
35
66 67 67 67 68 68 68 69 69 70
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xv
Contents
Chapter 4
The tab Element Required Attributes of the tab Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the tab Element Graphical View of tab Attributes Built-in Tabs Referring to Built-in Tabs Modifying a Built-in Tab Custom Tabs Creating Custom Tabs Positioning Custom Tabs The group Element Required Attributes of the group Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the group Element Graphical View of group Attributes Built-in Groups Referring to Built-in Groups Using a Built-in Group on a Custom Tab Custom Groups Creating Custom Groups Positioning Custom Groups Custom Groups on Built-in Tabs Conclusion
76 78 79 80 80 81 83 83 83 85 85
Introducing Visual Basic for Applications (VBA)
87
Getting Started with Visual Basic for Applications (VBA) What Is VBA? Macro-Enabled Documents Using the Visual Basic Editor (VBE) Recording Macros for Excel and Word A Recording Example Editing the Recorded Macro Editing Macro Options After Recording Subprocedures versus Functions Object Model Subprocedures Functions VBA Coding Techniques Looping Statements For-Next Loops Do-While/Do-Until Loops With . . . End With Statement If . . . Then . . . Else . . . End If Statement Select Case Statement
70 70 71 72 72 72 72 73 74 74 75 76 76
88 89 89 90 91 94 95 96 97 98 98 100 101 101 102 105 106 107 109
xv
91118ftoc.qxd:WileyRedTight
xvi
12/3/07
9:54 AM
Page xvi
Contents
Chapter 5
Writing Your Own Code Naming Conventions Data Types Working with Events Workbook Events Worksheet Events Form and Report Events in Access Document-Level Events in Word Application-Level Events The Object Browser Referencing Libraries Early and Late Bindings Explained Debugging Your Code Debug.Print and Debug.Assert Stop Statement Immediate Window Locals Window Watches Window Error Handling On Error Resume Next On Error GoTo Working with Arrays Determining the Boundaries of an Array Resizing Arrays Conclusion
110 111 112 114 115 117 119 122 123 125 126 128 129 130 131 132 134 135 137 138 138 140 141 142 143
Callbacks: The Key to Adding Functionality to Your Custom UI
145
Callbacks: What They Are and Why You Need Them Setting Up the File for Dynamic Callbacks Capturing the IRibbonUI Object Adjusting the XML to Include onLoad Setting Up VBA Code to Handle the onLoad Event Generating Your First Callback Writing Your Callback from Scratch Using the Office CustomUI Editor to Generate Callbacks Understanding the Order of Events When a File Is Open Can I Have Two Callbacks with the Same Name But Different Signatures? Calling Procedures Located in Different Workbooks Organizing Your Callbacks Individual Callback Handlers Using Global Callback Handlers Handling Callbacks in Access Using VBA to Handle Callbacks Using Macros to Handle Callbacks
145 146 147 147 147 148 148 150 151 152 153 155 155 157 158 158 160
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xvii
Contents
Chapter 6
Invalidating UI Components What Invalidating Does and Why You Need It Invalidating the Entire Ribbon Invalidating Individual Controls Conclusion
162 162 163 165 167
RibbonX Basic Controls
169
The button Element Required Attributes of the button Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the button Element Parent Objects of the button Element Graphical View of button Attributes Using Built-in button Controls A button Idiosyncrasy: The showLabel Attribute Creating Custom button Controls An Excel Example A Word Example An Access Example The checkBox Element Required Attributes of the checkBox Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the checkBox Element Parent Objects of the button Element Graphical View of checkBox Attributes Using Built-in checkBox Controls Creating Custom Controls An Excel Example A Word Example An Access Example The editBox Element Required Attributes of the editBox Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the editBox Element Parent Objects of the editBox Element Graphical View of editBox Attributes Using Built-in editBox Controls Creating Custom Controls An Excel Example A Word Example An Access Example The toggleButton Element Required Attributes of the toggleButton Element Optional Static and Dynamic Attributes with Callback Signatures
169 170 171 173 173 173 174 175 176 176 179 181 183 184 184 186 186 186 187 188 188 192 194 196 197 197 199 199 200 200 200 200 203 205 209 209 210
xvii
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xviii
xviii Contents
Chapter 7
Allowed Children Objects of the toggleButton Element Parent Objects of the toggleButton Element Graphical View of toggleButton Attributes Using Built-in toggleButton Controls Creating Custom Controls An Excel Example A Word Example An Access Example Conclusion
212 212 212 213 214 214 217 220 223
comboBox and dropDown Controls
225
The item Element Required Attributes of the item Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the item Element Parent Objects of the item Element Graphical View of item Attributes Using Built-in Controls Creating Custom Controls The comboBox Element Required Attributes of the comboBox Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the comboBox Element Parent Objects of the comboBox Element Graphical View of comboBox Attributes Using Built-in Controls Creating Custom Controls An Excel Example A Word Example An Access Example The dropDown Element Required Attributes of the dropDown Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the dropDown Element Parent Objects of the dropDown Element Graphical View of dropDown Attributes Using Built-in Controls Creating Custom Controls An Excel Example A Word Example An Access Example Conclusion
225 226 226 227 227 227 228 228 229 229 229 232 232 232 232 234 235 237 239 244 244 244 247 247 248 248 249 249 254 258 261
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xix
Contents Chapter 8
Custom Pictures and Galleries Custom Pictures Suggested Picture Formats Appropriate Picture Size and Scaling Adding Custom Pictures to Excel or Word Projects Using the Custom UI Editor Loading Custom Pictures On-the-Fly Adding Custom Pictures to Access Projects Using GDI+ to Load PNG Files Using the Gallery Control Example of Static Attributes Example of Built-in Controls Creating an Image Gallery On-the-Fly Conclusion
Chapter 9
Creating Menus The menu Element Required Attributes of the menu Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the menu Element Parent Controls of the menu Element Graphical View of menu Attributes Using Built-in Controls Creating Custom Controls An Excel Example A Word Example An Access Example The splitButton Element Required Attributes of the splitButton Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the splitButton Element Parent Objects of the splitButton Element Graphical View of splitButton Attributes Using Built-in Controls Creating Custom Controls An Excel Example A Word Example An Access Example The dynamicMenu Element Required Attributes of the dynamicMenu Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the dynamicMenu Element Parent Objects of the dynamicMenu Element
263 263 263 266 266 267 268 270 274 276 278 280 281 282
285 286 286 286 288 289 289 290 291 292 294 296 299 299 300 301 301 301 302 303 303 305 306 310 310 311 313 313
xix
91118ftoc.qxd:WileyRedTight
xx
12/3/07
9:54 AM
Page xx
Contents Graphical View of dynamicMenu Attributes Using Built-in Controls Creating Custom Controls Conclusion
Chapter 10 Formatting Elements The box Element Required Attributes of the box Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the box Element Parent Objects of the box Element Graphical View of box Attributes Using Built-in box Elements Creating Custom box Elements Horizontal Alignment Vertical Alignment Nesting box Controls The buttonGroup element Required Attributes of the buttonGroup element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the buttonGroup Element Parent Objects of the buttonGroup Element Graphical View of a buttonGroup Using Built-in buttonGroup Elements Creating Custom buttonGroup Elements The labelControl Element Required Attributes Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the labelControl Element Parent Objects of the labelControl Element Graphical View of a labelControl Using Built-in labelControl Elements Creating Custom labelControl Elements The separator Element Required Attributes of the separator Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the separator Element Parent Objects of the separator Element Graphical View of a Separator Using Built-in separator Elements Creating Custom separator Elements
313 314 314 320
323 324 324 324 325 326 326 327 327 327 328 329 333 334 335 336 336 336 336 337 338 338 338 340 340 340 341 341 344 344 345 346 346 346 346 346
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxi
Contents The menuSeparator Element Required Attributes of the menuSeparator Element Optional Static and Dynamic Attributes with Callback Signatures Allowed Children Objects of the menuSeparator Element Parent Objects of the menuSeparator Element Graphical View of the menuSeparator Element Using Built-in menuSeparator Elements Creating Custom menuSeparator Elements Conclusion
Chapter 11 Using Controls and Attributes to Help Your Users
Part II
347 347 348 349 349 349 350 350 352
355
The dialogBoxLauncher Element Required and Optional Attributes Allowed Children Objects Parent Objects Examples of Using the dialogBoxLauncher Element Built-in dialogBoxLaunchers A Custom dialogBoxLauncher with Built-in Dialogs Custom dialogBoxLauncher with Custom Userforms The keytip Attribute Creating a Keytip Keytip Idiosyncrasies screentip and supertip Attributes Creating screentip and supertip Attributes Overwriting Built-in Control Attributes Conclusion
356 356 356 357 357 357 358 360 362 363 364 366 366 368 369
Advanced Concepts in Ribbon Customization
371
Chapter 12 Advanced VBA Techniques Working with Collections Determining Whether an Item Belongs to a Collection Class Modules Properties, Methods, and Events Working with Properties Working with Methods Working with Events Web Services and CustomUI Using VBA Custom Properties Setting Up the Custom Properties Saving and Retrieving Values from the Registry Conclusion
Chapter 13 Overriding Built-in Controls in the Ribbon Starting the UI from Scratch Setting the startFromScratch Attribute Activating a Tab at Startup Disabling and Repurposing Commands
373 373 377 378 378 379 380 382 383 389 389 394 399
401 402 402 404 406
xxi
91118ftoc.qxd:WileyRedTight
xxii
12/3/07
9:54 AM
Page xxii
Contents Disabling Commands, Application Options, and Exit Disabling Commands Disabling the Commands Associated with the Application Options and Exit Controls Repurposing a Command Associated with a Generic Control Affecting the Keyboard Shortcuts and Keytips Conclusion
Chapter 14 Customizing the Office Menu and the QAT Adding Items to the Office Menu Adding Items to the QAT Customization Overview sharedControls versus documentControls Adding Custom and Built-in Commands to the QAT Adding Custom and Built-in Groups to the QAT Repurposing QAT Controls Table-Driven Approach for QAT Customization (Excel and Word) Table-Driven Approach for QAT Customization (Access) QAT Caveats Inability to Load Controls Inability to Load Custom Images to Controls Duplication of Controls on XML-Based and XML-Free Customizations Conclusion
Chapter 15 Working with Contextual Controls Making Your Items Contextual Tabs Groups Working Through Nonvisibility Methods Enabling and Disabling Controls Working with Contextual Tabs and tabSets Creating a Custom Contextual Tab in Access Renaming a tabSet Modifying Built-in Contextual Tabs Working with Contextual Pop-up Menus Replacing Built-in Pop-up Menus in Their Entirety Adding Individual Items to Pop-up Menus Multilingual UI Conclusion
Chapter 16 Sharing and Deploying Ribbon Customizations Excel Deployment Techniques Distributing Workbooks Using Templates Creating and Deploying Add-ins Preparing a Workbook for Conversion to an Add-in Converting a Workbook to an Add-in Format
406 406 407 408 410 412
413 413 418 418 419 420 422 424 428 430 433 433 434 434 435
437 437 438 439 441 441 442 442 444 445 447 448 453 455 458
459 460 460 461 463 464 465
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxiii
Contents xxiii Installing an Add-in Unloading and Removing Add-ins Toggling the IsAddin Property A Note on the PERSONAL.XLSB Workbook Word Deployment Techniques Distributing Documents Using Templates Configuring Template Directories Creating Templates Global Templates Preparing a Document for Conversion to a Global Template Converting a Template to a Global Template Editing Global Templates Removing Global Templates A Note on the Normal.dotm Template Sharing Ribbon Items Across Files(Word and Excel) Creating a Shared Namespace Sharing Tabs and Groups in Excel Sharing Tabs and Groups in Word Deploying Word and Excel Solutions Where Multiple Versions of Office are in Use Do Legacy CommandBar Customizations Still Work? Method 1: Creating Separate Versions Method 2: Calling a Previous Version from a New Add-in Using a 2003 Excel Add-in as a Front-End Loader for a 2007 Add-in Using a Word 2007 Global Template as a Front-End for a 2003 Template Access Deployment Techniques General Information Concerning Database Deployment Preparing the Files for Multi-User Environments Managing Access Startup Options Leveraging the startFromScratch Attribute Adjusting Access Options for Your Users Creating an ACCDE File Loading the customUI from an External Source Deploying Solutions to Users with Full-Access Installations Deploying Customizations with Full Versions of Access Deploying Solutions to Users with the Access Runtime Version Conclusion
Chapter 17 Security In Microsoft Office Security Prior to Office 2007 Macro-Enabled and Macro-Free File Formats The Trust Center Trusted Publishers
465 467 467 468 469 469 470 470 471 472 473 474 475 476 476 477 478 479 485 491 491 492 493 494 500 504 504 504 507 507 508 510 511 514 514 518 519
523 524 524 525 526
91118ftoc.qxd:WileyRedTight
xxiv
12/3/07
9:54 AM
Page xxiv
Contents Trusted Locations Adding, Modifying, or Removing Trusted Locations Trusting Network Locations Disabling Trusted Locations Add-ins Requiring Add-ins to Be Signed Disabling Notification for Unsigned Add-ins Disabling All Add-ins ActiveX Settings Macro Settings Setting Macro Options Trusting VBA Project Access Message Bar Privacy Options Digital Certificates How Digital Certificates Work Acquiring a Digital Certificate Using SELFCERT.exe to Create a Digital Signature Adding a Digital Certificate to a Project Trusting a Digital Certificate on Another Machine Deleting a Digital Certificate from Your Machine Conclusion
Appendix A Tables of RibbonX Tags How to Use This Appendix Ribbon Container Elements customUI Element ribbon Element contextualTabs Element tabSet Element qat Element sharedControls Element documentControls Element officeMenu Element tabs Element tab Element group Element Ribbon Control Elements box Element button Element buttonGroup Element checkBox Element comboBox Element dialogBoxLauncher Element dropDown Element dynamicMenu Element
526 528 529 529 529 530 530 531 531 532 532 533 533 534 534 534 535 536 537 538 540 542
545 545 546 546 548 548 548 549 549 549 549 550 550 551 552 553 553 556 557 558 562 562 566
91118ftoc.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxv
Contents editBox Element gallery Element item Element labelControl Element menu Element menuSeparator Element separator Element splitButton Element toggleButton Element
568 571 575 576 577 579 580 581 582
Appendix B Tables of Tab and Group idMso Names
587
Common Tab idMso Identifiers Contextual Tab idMso Identifiers Contextual Tab idMso Identifiers for Excel Contextual Tab idMso Identifiers for Access Contextual Tab idMso Identifiers for Word Group idMso Identifiers Excel’s Group idMso Identifiers Access’s Group idMso Identifiers Word’s Group idMso Identifiers
Appendix C imageMso Reference Guide How to Get Your Own imageMso References Your Own Reference Tool
Appendix D Keytips and Accelerator keys Keytips and Accelerator Keys for Excel
587 588 588 589 590 590 590 595 600
607 607 608
611 611
Appendix E RibbonX Naming Conventions
615
How Our Naming System Works Naming Samples
615 617
Appendix F Where to Find Help Websites with RibbonX Information Websites Maintained by the Authoring and Tech Edit Team Newsgroups Web Forums
Index
621 621 623 623 624
627
xxv
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxvi
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxvii
Acknowledgments
We’ll start by thanking everyone who helped research, write, test, and refine our material. We began as a team of two Excel and one Access MVPs, and later recruited Oliver Stohr, Access MVP, to write the Access deployment section. We send a tremendous note of appreciation to Oli for stepping up and sharing his expertise. In addition, because the book covers three, not just two applications, we also enlisted the expertise of two Word MVPs. Therefore, we send a special thanks to Tony Jollans, for always responding to our inquiries and assisting whenever we asked, and to Cindy Meister, for writing the macro to update all the document fields. But writing is just the first stage in the journey. We relied on our remarkable team of technical editors, Jeff Boyce (Access MVP) and Nick Hodge (Excel MVP), to check and challenge our writing. They each added a particular expertise to the book. Jeff’s incessant questions may have threatened to drive us crazy, but they ensured that we provided ample background when introducing new material so that users of any level could understand and work through the examples. Nick’s contributions went far above and beyond his role. His fingerprints are all over the book, and it truly would not be what it is without him. Nor would we have this book if it weren’t for the great people at Wiley. We had the great fortune of working with Tom Dinse as our development editor. In addition to making sure that our material was consistent, properly sequenced, and fit the guidelines, Tom went out of his way to get answers and find ways to make things work. And Luann Rouff, our copy editor, was remarkably attentive formatting and our deadlines— definitely not an 8 – 5 worker! We also send a very special thanks to Bob Elliott for his expertise in shaping the proposal and working through the administrative processes. We thank Colleen Hauser for inviting us to participate in designing the cover. There are many others, even those that we haven’t yet met, who deserve our appreciation and thanks for helping to take this book from a concept to reality. Writing this book has been a challenging and incredibly rewarding experience. It was only possible with the help of dozens of people and by pulling together as a team. Thank you, all! — The Authors
xxvii
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxviii
xxviii Acknowledgments I thank my girlfriend for putting up with me during a very stressful period. — Robert Martin I’d like to thank my wife, Deanna, and my boss and friend, Jim Olsen, for their encouragement, support, and the latitude they have given me to pursue my quest for knowledge. Without these two people behind me, none of this would have ever been possible. — Ken Puls I want to say what a great privilege it has been to work with Robert and Ken. They poured out the pages and maintained wonderful humor as I tried to meld everything together into a consistent voice and style. Thank you, guys, so very much; coordinating this book and working on a truly international team is another opportunity that I shall cherish forever. In addition, I can’t adequately express my appreciation for Oli. When we needed very special expertise, Oli responded and immediately set to work on the Access deployment section. I also have to send a personal thank-you to Bob Elliott and Tom Dinse. I feel incredibly fortunate that you were both guiding our way. My family and very special friends have my heartfelt appreciation for encouraging and “being with me” through my adventures, and I send a tremendous thanks to my clients for hanging in there when it seemed that I barely had time to take their calls. Most of all, I want to thank my mom, my papa, and my dad for being the caring people and stellar citizens that they are. Three of their sayings come to mind, and I’m sure that they will ring true with you: “Your word is your honor”; “A job worth doing is worth doing right”; and “Do what you enjoy, and you’ll be good at it.” — Teresa Hennig
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxix
Introduction
Welcome to the wonderful world of Ribbon customizations. With the advent of Office 2007, users and developers alike are faced with major transitions. Office applications have a whole new look, and the Ribbon is the headliner. While it may have strong appeal to new users, adapting to the Ribbon can be challenging to those of us who are accustomed to the legacy menu and toolbar system. With the advent of the Ribbon, you not only lose the drop-down menus that were always there, but the custom menus and toolbars have also departed. However, we are not stuck with Microsoft’s Ribbon which can appear overwhelming with thousands of commands; and you don’t need advanced training or mastery of coding languages to tailor the Ribbon to your needs. That’s where this book comes in. Driven by their passion for helping others to enjoy and maximize the experience of working with Office applications, the team of authors has created a resource that enables both users and developers to customize the Ribbon to the extent that they desire. Whether that means replacing the Ribbon with familiar toolbars from prior versions, using photos and labels to personalize commands, or merely creating custom groups of existing tools, the book provides instructions and working examples so that you have the tools and confidence to accomplish your goals. Not only that, but the task can be accomplished using XML and VBA, so there is no need to purchase expensive software or learn complicated programming languages. You will see that customizing the Ribbon can be fun and rewarding. If you are a developer, you will be empowering users to work more efficiently and you will have new opportunities to tailor both the functionality and the look to specific solutions. Power users will appreciate creating time-saving commands that can easily be shared with associates. In addition, if you are creating customizations for your own purposes, you will enjoy the latitude to truly personalize the appearance of the commands as you structure them for your convenience. In addition to walking you through the stages for customizing the Ribbon, we also demonstrate how to work with legacy tools. Each chapter presents various options, along with their benefits and drawbacks, so that you can make informed plans and decisions.
xxix
91118flast.qxd:WileyRedTight
xxx
12/3/07
9:54 AM
Page xxx
Introduction
Overview of the Book and Technology Programming and customizing the Ribbon requires a new set of skills, and because the Ribbon is not compatible with previous custom menus and toolbars, developers and power users will immediately begin to seek ways to customize the Ribbon. We are all faced with the choice of either working with the standard Ribbon, replacing the Office Ribbon with a custom Ribbon, or foregoing the Ribbon and installing custom menus and toolbars from previous applications. RibbonX: Customizing the Office 2007 Ribbon has two key target audiences. The obvious one is power users and developers working in Excel, Access, and Word. However, because average users continue to expand their ability to control their environment, the examples are presented so that a typical user will also be able to work through and implement the processes in total confidence. Therefore, in addition to providing key tips and techniques that developers expect, this book also contains sufficient introductory materials to enable any user to follow the examples and begin to benefit from the efficiencies that can be obtained with a customized Ribbon. By the time you finish this book and the demo projects, you will have both the knowledge and confidence to customize the Ribbon for the three major components in Office: Excel, Access, and Word. Although the customization process is pretty much the same for the XML code, that doesn’t mean the steps are the same for each application — so we scrupulously point out the similarities and differences. RibbonX: Customizing the Office 2007 Ribbon concentrates on the following: ■■
XML development for Ribbon customization
■■
Using VBA to add functionality to the custom UI
We are sure that readers will enjoy this book and find it extremely useful as a reference tool for UI customization. It will certainly change the way that one views the new Office UI.
How This Book Is Organized RibbonX is sure to become the definitive guide to customizing the Ribbon for Excel, Access, and Word. The book is a major resource for power users and developers. It covers the basics in enough detail that even if this is your introduction to writing code, you will learn what you need to know, understand the concepts, and be able to develop and share custom Ribbons and Ribbon components that best suit your needs or those of the intended user. The material and examples are created using Excel, Access, and Word. We identify when the same procedure applies to all three programs, and we point out any differences among them. The chapters are filled with real-world examples and sample code, so readers will immediately be able to apply their new skills. In many cases, a separate example is used to demonstrate how to incorporate the same feature into each application: Excel, Access, and Word.
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxxi
Introduction The following is a brief summary of what each chapter covers. Although you may not recognize some of the terminology yet, be assured that comprehensive explanations are provided as material is introduced. We also leverage notes, cross-references, and other notes to guide you to reference material and points of particular interest. To help you build a solid foundation, we start with some background material, covering essential programming processes that you’ll need to be familiar with. Chapter 1 discusses the user interface and points out some of the challenges associated with transitioning from menus to the Ribbon, as well as some of the benefits gained with the new UI. Chapter 2 briefly introduces the key components for customizations and how to work with them. You’ll learn about XML files, the Office Custom UI Editor, and the USysRibbon table. In Chapter 3, we lay the foundation for working with XML and explain the various containers that we’ll be using — namely, the ribbon, tabs, tab, and group containers. Because this is the first time that developers have been required to use XML with their projects, this chapter will be a handy reference to turn to as you work through subsequent chapters and exercises. Chapter 4 covers the essentials for working with Visual Basic for Applications code (VBA). If you are an experienced developer, you may breeze right past this chapter, but readers new to programming will learn what they need to know to create successful customizations, including how to work with arrays. Next, in Chapter 5, we introduce callbacks, which are essentially the hook between the commands on the Ribbon and the code that needs to run. We also explain the processes for invalidating commands and the Ribbon. (Invalidation is a frequently used term throughout the book, so it’s important to understand what it is and how it works.) After that, we start creating new controls. In Chapter 6, you will actually create custom Ribbon commands, beginning with the most common command, the button. We walk you through creating a button, checkBox, editBox, and toggleButton. Then, in Chapter 7, we explain container controls and show you how to create them. Container controls, which can hold other controls, include the splitButton, the item, the comboBox, and the dropDown control. Then the creativity really gets to flow in Chapter 8. That’s where you learn how to use the icon gallery and add highly personalized touches, such as including custom images in the Ribbon commands. From there, we move to on to the menu controls in Chapter 9. These are powerful controls, such as the Save As control, that can contain a combination of controls and labels. This means that in addition to creating the controls themselves, you’ll become accomplished at formatting the controls to add groupings and clarity. Chapter 10 provides additional formatting tools, such as the box element, which essentially creates an invisible box for grouping controls. After that, we demonstrate how to place a visible box around a group of controls by using the buttonGroup. To complement these groups, we show you how to use the labelControl to add a heading or a description of other controls. Of course, boxes are not always the ticket, and sometimes just a line is preferable. That’s when you’d use the Separator element or the menuSeparator element to insert either a vertical line or a horizontal line, respectively. That brings us to the last chapter of our fundamentals section. Chapter 11 covers features that provide that extra level of assistance needed to have highly intuitive user interfaces. It also shows you how to make custom help and tips available when users
xxxi
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxxii
xxxii Introduction need them the most. This includes attributes such as the keytip, the screentip, and the supertip. We cap it off by showing you how to modify built-in controls. The next portion of the book deals with advanced concepts. This starts with Chapter 12, which teaches you some advanced VBA techniques that enable you to work with multiple controls at one time. After explaining collections, we demonstrate how to use properties, methods, and events. Then, as somewhat of a bonus, we’ll show you how quick and easy it is to incorporate Web services right into your UI, thereby bringing online resources to the user’s fingertips. With all this material under your belt, you are ready to get rid of the Ribbon and literally “start from scratch.” That’s actually the name of the attribute that removes the built-in Ribbon and commands from the UI. Of course, it isn’t as drastic as it might sound, because the Office button and several other commands are still available through shortcuts. Chapter 13 covers all of that and demonstrates how to build a completely custom Ribbon. However, that isn’t the end, because the Ribbon also introduced the Quick Access Toolbar (QAT). The QAT is probably the closest thing on the Ribbon to what we fondly remember as toolbars; and the QAT was designed to do exactly what the name implies, to provide users with instant access to the tools that they most often use. Because the QAT is not context sensitive, the tools are always in the same place. Chapter 14 provides detailed steps for working with and organizing the QAT. With the Ribbon, the QAT is just the tip of the iceberg when it comes to putting controls at the user’s fingertips. We take this to the next level in Chapter 15, where you learn how to create context-sensitive tabs, groups, and controls. These powerful little tools are the best replacement for custom pop-ups. In addition to creating new contextual tabs, we also show you how to modify built-in contextual tables. With those skills, the choice is yours: either start fresh or merely tweak and add to what is provided. After we’ve pretty much covered the gamut of customizations, it is time to turn our attention to sharing our solutions. Chapter 16 discusses the issues associated with preparing files and packaging them for deployment. This chapter includes detailed guidance about the reasoning behind the process and walks you through the steps for each application. In addition to deploying complete solutions, we also explore the final RibbonX attribute: The idQ is geared toward sharing elements across files, and it enables users to load and unload Ribbon customizations from a central source. The final chapter in the book, Chapter 17, explains the security issues that can affect creating, using, and sharing Ribbon customizations. We explain some of the rationale for various security measures, as well as how they can impact your development work and the end user. You also learn about macro-enabled and macro-free file formats, trust centers, and trusted locations. In addition, of course, we share recommendations for providing steps to ensure that users have the appropriate security settings and are still able to enjoy the benefits provided by your customization efforts. To cap everything off, we include appendixes of reference material so that readers have everything they need at their fingertips. Appendixes cover such topics as naming conventions, and list the correct names for groups, tabs, and other necessary objects in the Ribbon and application hierarchies. As you can see, this book is everything that we said it would be. It provides the information that you need to immediately achieve results, and it will become an invaluable reference for future projects.
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxxiii
Introduction xxxiii
Why Read This Book This book addresses issues that are daunting multitudes of developers and end users alike. It provides the information and examples that will prepare you to create and share intuitive custom UIs, and it demonstrates the options available for working with legacy custom menus and toolbars. RibbonX: Customizing the Office 2007 Ribbon is a one-stop reference for anyone who wants to customize the Ribbon. With that goal in mind, it not only works through customizing the Ribbon, but also covers related material. That means the book discusses the basics for working with VBA, XML, and macros. In addition, because macros and VBA trigger Windows security settings, the book also reviews relevant aspects of security, such as trust centers and digital certificates. It also explains issues and processes associated with preparing and packaging customizations for deployment in target environments, including detailed instructions for deploying Access run-time installations. Whether you are an end user, a seasoned Office developer, or somewhere in between, this book is the perfect reference for customizing and programming the Ribbon.
Tools You Will Need One of the beauties of this book is that if you have Office 2007, you’ve already made the prerequisite purchase. That’s because we leverage the XML and VBA capabilities that are intrinsic to Microsoft Office 2007, and the additional tools that are available as free downloads from Microsoft. Therefore, contrary to common perception, you can customize the Ribbon without purchasing expensive developer software, such as Visual Studio Tools for Office, and without learning complicated coding languages such as C#. Your customizations will run on essentially all installations of Office 2007. They can be developed and deployed on both Windows Vista and Windows XP platforms, and it matters little whether these are standard installations or are on virtual machines. In short, essentially anyone with Office 2007 will be able to work through the numerous examples for Excel, Access, and Word.
What’s on the Website In addition to the material provided in the book, the chapters are supplemented by sample files that can be downloaded from the book’s website, www.wiley.com/go/ ribbonx. We strongly encourage you to download the files and use them as you work through the exercises. The online files are invaluable resources that not only provide working demonstrations of the material being covered, but also serve as an ultra-handy source for code snippets. In addition to studying the code in the text, you can use the downloads to view the XML and VBA in their native formats, and you can copy it directly into your own projects.
91118flast.qxd:WileyRedTight
12/3/07
9:54 AM
Page xxxiv
xxxiv Introduction Some of the files are also nice bonuses in and of themselves. For example, in Appendix C, not only do we provide the source code and file, but our example is actually a fully functional tool that will locate, group, and display the imageMSO for each application. Although you may not realize the value of that yet, you soon will — when you discover that the imageMSO is an integral part of Ribbon customizations. Robert’s tool in Appendix C is just one example of how the authoring team has taken extra steps to provide you with the information and resources that you’ll need.
Congratulations We are excited about the opportunity to help fellow developers and end users to customize the Ribbon and create solutions that are both personalized and enhance productivity. You will likely be surprised by how easy it is to create customizations, not only for your own use, but also to share with others. An exciting bonus for end users is that in addition to customizing your work environment, you will be expanding your horizons, adding new programming skills, and building the confidence to take on bigger and more challenging projects. We recommend that as you go through the book, you download the sample files so that you can see it in its native environment, see how a finished example will work, and experiment with changes. Working through the exercises will enable you to take ownership of the concepts and incorporate them into your customizations. Learn, enjoy, be creative. Build solutions that work for you.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Page 1
Part
I The Building Blocks for a Successful Customization
In This Part Chapter 1: An Introduction to the Office User Interface Chapter 2: Accessing the UI Customization Layer Chapter 3: Understanding XML Chapter 4: Introducing Visual Basic for Applications Chapter 5: Callbacks: The Key to Adding Functionality to Your Custom UI Chapter 6: RibbonX Basic Controls Chapter 7: RibbonX Basic Container Controls Chapter 8: Custom Pictures and Galleries Chapter 9: Creating Menus Chapter 10: Static and Dynamic Menus Chapter 11: Making Use of Controls and Attributes to Help Your Users
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Page 2
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Page 3
CHAPTER
1 An Introduction to the Office User Interface
When you open Office 2007, you are welcomed by a totally new user interface (UI). Although the new UI brings immediate productivity improvements to new Office users, it can take a few days or weeks for experienced Office users to adapt to and become fluent with the new interface. But it is definitely worth the investment to get up to speed with the new UI, particularly the Ribbon. Knowing what to expect and understanding the logic behind the Ribbon will make life much easier. This chapter is designed to familiarize you with the Ribbon so that you can understand how to work with it from the perspective of an end user as well as from the perspective of a power user and developer. There are such dramatic changes to the UI that we highly recommend that even power users and developers read through this introductory chapter. It provides an important foundation, as it explains the Ribbon’s background. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
What Is the Ribbon and Why Does It Exist? Office 2007 has been given a face-lift! And what a makeover! For those who got used to the old toolbars, the new UI can be a tremendous shock. However, as you learn the new interface, you will start to appreciate its benefits and, hopefully, embrace it as we and countless of others have done.
3
91118c01.qxd:WileyRedTight
4
Part I
■
11/28/07
7:33 PM
Page 4
The Building Blocks for a Successful Customization
Microsoft invested a significant amount of time and money in the creation of the new Ribbon interface. Millions of dollars were spent tracking user trends, including how people actually used the Office applications and their underlying hierarchical menus. They studied where users were going, what commands they used, and in which order. They even tracked eye movement to see where people tend to look first to find a command. The results were used to develop the Ribbon. The introduction of hierarchical menus was a great leap from the top-level menu style. One factor prompting its development was that top-level menus were running out of space for additional commands for the new features as they were added to an application. With the introduction of hierarchical menus, more room was available for the new commands being created. What this meant, of course, was an increase in the number of features displayed from one version to another. But just as there is no free lunch in economics, there is no free lunch with such an approach. As the number of features increased, it became more difficult to actually find them due to the increased complexity of the UI. If you think back to the very first versions of Word, for example, there was really no hierarchy as such. It had the top-level menu, and a user could quickly scan its contents for the needed feature. By the time we reached Word 97, things had changed dramatically and problems with the UI started to appear. At this point, the future started to look bleak for the hierarchical order. A change in paradigm became necessary. It was no longer a matter of whether it would happen, but rather when it would happen.
Problems with the Old UI As the UI moved from top-level to hierarchical menus, things started to get out of control and people were frequently searching for commands. It was often so challenging to find a command that most users had difficulty in locating desired options, or perhaps worse, they would give up on trying to find something that they knew was there. Take for example the Insert ➪ AutoText ➪ Header/Footer hierarchy in Word 2003, shown in Figure 1-1.
Figure 1-1: Hierarchical menu in Word 2003
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 5
■
An Introduction to the Office User Interface
Chances are good that unless you’re very experienced with Word, you would have never known that this option even existed. Hence, we end up with a huge dilemma on our hands: We have a feature-rich application, but just a portion of these features are being used because they are buried under extensive branching and a burdensome hierarchy. Thus, not only are many features unused, but it also becomes cumbersome for experienced users to keep track of things under such scenarios. Users are all too familiar with the frustration of wasting time hunting for the command to execute a needed feature. Microsoft tried to address this issue when it became obvious back in Office 97. With Office 2000, they introduced the concept of adaptive menus. The idea was that the top-level menu would be shorter and only show the features that the user accessed most frequently. This selection process would translate into a UI that reflected a user’s true daily needs instead of presenting dozens of commands that he never used and did not care about. For anyone who was a bit more fluent in the UI from Office 97, this new feature actually became a hindrance. And for new users, it translated into frustration. Ultimately, a lot of users were switching off this feature and leaving the menus to show in full instead of relying on the adaptive menus technology to figure out and create a UI tailored to their actions. To make matters worse, the introduction of full menu customization meant that users could move toolbars around, dock them in some silly place, float them on the screen, close them altogether, and perform a whole bunch of other actions such as adding and removing controls from the toolbars and menus at will. This may look great from the point of view of a power user or programmer, but the world is not made up of power users and programmers alone. Not everyone is technically savvy and this had to be taken into account too. As anyone who has had to work the help desk for Office applications knows, many end users would close toolbars, never to find them again. In addition, users were prone to float toolbars, impairing their ability to see the document. At that point, the user would become frustrated and call the help desk with a complaint. Of course, the help desk staff and many developers might be astounded that someone would do these things — and then admit it. But we’ve all heard hilarious stories about the CD tray breaking under the weight of a coffee cup, so as crazy as the stories sound, they’re true, and they provide valuable information to developers — one of which is that features need to be as clear and intuitive as possible. Because of the confusion and difficulties the average user could experience, it was obvious that something had to be done. The support calls and other challenges triggered the overhaul of the UI as we knew it. A paradigm shift seemed more and more necessary. With the release of Office 2007, the new Office Fluent UI was provided to address such issues. However, as might be anticipated with such a dramatic change, it can be met with resistance from those who are familiar and comfortable with the old ways. In addition, it has created new challenges for power users and developers who want to deliver a custom UI. This book tackles the issues in a manner that will be comfortable for and will benefit all levels of users and developers. If you are a beginner, you will love this book because you learn the fundamentals and will be able to customize your own workspace. If you are a power user, you will love this book because you will be able to work more efficiently by customizing and sharing your Ribbon components. And if you are a developer, you will also love this book because you will be able to leverage your custom components, work across multiple applications, incorporate third-party tools, and deploy contemporary solutions.
5
91118c01.qxd:WileyRedTight
6
Part I
■
11/28/07
7:33 PM
Page 6
The Building Blocks for a Successful Customization
So, it’s time to discuss the old issues addressed by the new UI as well as the new issues that were created by it.
Issues Solved with the New UI What came out of the massive budget and research on Microsoft’s part when it was manifested in the Ribbon? Many painful problems in the old user interface were solved, including the following: ■■
Lack of context: Wouldn’t it be nice to have the controls you need displayed when you need them? That’s what contextual controls are all about. In the older versions of the applications, all commands were buried somewhere — but not necessarily where users would intuitively expect them to be. In addition, only a few features (Pivot Tables and Charts in Excel, for example,) had contextual commands that would display when the feature was being used. In the new UI format, Microsoft attempts to be much more contextual, and to present commands in a way that is consistent with what their research indicated would be the next logical step. Contextual controls are discussed in Chapter 15.
■■
Command accessibility: Microsoft seems to have put in a huge amount of effort to make the commands more “discoverable” to new and less familiar users. In fact, even power users will discover commands that they didn’t know existed in prior versions. Commands have been logically grouped together, which was not always the case in the past. In addition, many features that used to be buried and difficult to find have been moved to the front lines.
■■
Customizations left behind: One of the major pain points in Office 2003 and prior versions was that a poorly designed customization could leave parts of the UI customizations exposed when a file was closed. For example, they might inadvertently change the default menu and toolbars for the user’s applications, possibly hiding or removing commands and toolbars — and many users would not know how to recover them. These problems are all gone now, thanks to the new UI format. The customizations are specific to the container files. As soon as you navigate away from a file that contains customization code, the customizations disappear in the UI. Close your file or even navigate to a new one and the customizations are tossed until you return. While you can share customizations across files via add-ins, global templates, and shared workspaces, the fundamental rule still remains that when that file is closed, the custom UI is unloaded with it. Unlike prior versions, no hangovers are left behind to haunt users.
■■
Corrupted toolbars: By virtue of not having toolbars to customize, the whole problem of corrupted toolbars has disappeared. The disappearance of this feature may seem like a curse for many, but in fact many of these files were quite fragile and could cost users countless hours to rebuild. Because all customizations are now built right in to the file, they also go with the file when it is sent to a new location, eliminating any worry about how the UI will be transferred or shared. The secret to building your own custom UI for users everywhere now becomes the task of building a much more stable add-in or global template, and that is what this book will teach you how to do.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 7
■
An Introduction to the Office User Interface
Issues Created with the New UI While the new user interface has undoubtedly solved many of the issues that were present in previous versions of Microsoft Office, it has naturally created some new ones that we must deal with. But, hey, this is a new technology and as with any new technology it will need to be adapted and improved along the way. One drawback (and the most immediately obvious one) about the Ribbon is that it takes up a lot of screen space, which, in itself, can defeat the purpose of giving the user a full experience of the Ribbon’s potential. Therefore, the higher the resolution of your screen, the more you will enjoy and benefit from your experience with the Ribbon. Figure 1-2 shows the Ribbon on a screen with a lower resolution. Some of the groups are collapsed; therefore, it is not possible to know what commands these groups contain until they are expanded.
Figure 1-2: Lower resolutions or minimized windows force Ribbon groups to collapse.
N OT E Resizing the window to a smaller size will cause the groups to collapse. The UI has four types of groups (large, medium, small, and collapsed) that come into play when the window is resized or when you’re working in a lowerresolution environment. A screen with a higher resolution, as shown in Figure 1-3, provides a greater visible area of the Ribbon, so the groups are all nicely displayed (compare the Styles group in Figure 1-2 and Figure 1-3).
Figure 1-3: Higher resolution allows for expanded groups.
Hence, the higher your monitor’s resolution, the more you will benefit from the Ribbon (the Ribbon is best visualized in a resolution of 1024 × 768 or higher). As you can see, this first issue is a visual one — that is, you do not need to know anything about the Ribbon to figure that out by yourself.
N OT E Although the Ribbon occupies a lot of space, you will notice that compared to other versions, the actual document visibility is larger for out-ofthe-box installations compared to older versions, as you will not have items such as horizontal scroll bars, and so on, showing up unnecessarily.
7
91118c01.qxd:WileyRedTight
8
Part I
■
11/28/07
7:33 PM
Page 8
The Building Blocks for a Successful Customization
Another visual issue relates to how much is actually available under each tab. You may find yourself moving back and forth along the Ribbon to get certain tasks done. The original goal was to make commands readily available, so this seems to defeat part of the purpose. And what if the user minimizes the Ribbon? You gain visible screen space, but you end up with something akin to the old menu hierarchy. As Figure 1-4 shows, minimizing the Ribbon gives the impression that you are still under the menu hierarchy. We discuss later in this chapter the collapsing and expansion (minimization and maximization) of the Ribbon.
Figure 1-4: Minimizing the Ribbon gives the impression you are still under the menu hierarchy.
Following are some of the nonvisual issues with the new UI: ■■
Less efficient for power users: Although the Ribbon gives end users instant access to the tools they need to perform their jobs more productively and professionally, it initially causes significant frustration among power users and developers. The flexibility found in previous versions in terms of easy customization is now history. Some of the issues (such as ease of customization) are already being addressed by Microsoft, but how far they will go only time will tell. For now, we have to live with what we have and make the best use of it. Power users and programmers will be happy to know that this book provides solutions to several of the issues, and it provides full examples of how to create and incorporate customizations.
■■
Fewer commands on screen: The controls that were determined to be the most frequently used are grouped and prominently displayed. As mentioned earlier, screen resolution can play a positive or negative role here because groups will collapse to fit into a smaller display. To make matters worse, you still need to navigate between the tabs. In addition, many tasks may still require navigating between tabs, such as when you need to validate data and then format your work in Excel, and during any type of database design activities.
■■
Lack of “tear away” toolbars: This is both a blessing and a curse in disguise. It is a blessing for beginners who will no longer suffer from misplaced toolbars (the story of the disappearing toolbar). Conversely, it is a curse for power users and developers who no longer have the flexibility to dock toolbars according to a specific need, as well as for those users who want to put the tools closer to their workspace to minimize mouse travel.
■■
Customization requires programming or third-party tools: This statement is not entirely true if you could not care less about your productivity. You might sit in front of your PC, open Notepad, and type away until you create an entirely new UI. But if you are even a little concerned with productivity (as we assume you must be), you will find that without a third-party customization tool your life could quite easily become a living hell . . . and we do mean that, despite the fact that this book will give you a taste of paradise and show you how to actually get there.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 9
■
An Introduction to the Office User Interface
■■
Table-driven menus are not what they used to be: Previously, a custom UI for an Office application could use external sources (such as tables in Access or Word, and worksheets in Excel, etc.) to lay out the UI, and then the UI could be loaded on-the-fly through a standard VBA procedure. Although this is not completely impossible to do with the Ribbon, the scope has been dramatically reduced compared to what it used to be. The reason for this limitation comes from the fact that the Ribbon UI is loaded at design-time and not at run-time. Chapter 14 includes a detailed discussion about how to do such things and what circumstances will limit the options.
■■
Commands may not be in the order you need them: It’s estimated that many Excel users know about 20% of its features, but that they each use a different 20% based on their jobs and experience. Because of this, Microsoft’s usage studies, despite being across many thousands of users, may not have captured your needs. Plainly put, the commands may not always be where you need them, and you may spend significant time searching through tabs to find them.
■■
Only one shot to create your UI structure: Unfortunately, there is currently no way to repopulate an entire tab, or group with controls in a dynamic manner. While you can hide or show groups (or enable/disable commands) that you’ve lain out at design time, you cannot dynamically add new tabs, groups, or controls to those groups on-the-fly. It’s true that you can dynamically populate certain controls with content, but this is only one very small part of the new UI. The inability to build tabs and groups on-the-fly is probably the largest piece missing from the Ribbon from a developer’s perspective, and it prevents us from building the truly fluent and rich UI that should be possible.
What Happened to the Toolbars from My Pre-2007 Files? It would be a nightmare to wake up to the new UI just to find that all your previous work would be pretty much worthless. Although for some this may be partially true, it is not necessarily the end of the world. Being forced to live with the Ribbon glued firmly in one place may not meet the unusually high standards and expectations of users who are accustomed to the docking capability. However, this “gluing” process is, in effect, quite sensible given that the majority of users (not power users and developers) have had a tendency to wreak havoc in the old UI by scattering toolbars all over the place, hiding toolbars and not being able to retrieve them, and in general creating UI mayhem. The Ribbon eliminates all that, but in the process it creates frustrations for more powerful users, as we have to learn new habits. As you know, old habits are hard to break. At this point you may be wondering a few things about your old customization. It is hoped that answering a few questions will dispel most of your doubts: ■■
Will your old customizations still work? Yes, they will, but they will certainly not look like anything you had before.
9
91118c01.qxd:WileyRedTight
10
Part I ■■
■
11/28/07
7:33 PM
Page 10
The Building Blocks for a Successful Customization
Where do they show up? If you’re planning to deploy a full-blown VBA-based customization, this new customization will be added to a new tab called AddIn. Although your project may not be an add-in, that’s what the tab is named and your UI can be filled with them. This is as good as it gets, at least for now. There are exceptions to AddIns, such as pop-up menus (pop-up menus are covered in Chapter 15).
Is anything else happening to the old way of doing things? The answer is a resounding YES! Developers will be happy to know that the programming model for legacy CommandBars has been upgraded so that it contains some new methods you can use to interact with the new UI. For example, we now have the GetImageMso function (method), which enables you to capture the image of a control (such as HappyFace) and place it somewhere else. This method is similar to the CopyFace method normally used to copy the face of a button in the old UI. That may seem like Greek to a lot of you, but rest assured that this book is designed with you in mind. It covers the fundamentals, explains the scenarios, and then steps through the examples so that not only will you be able to incorporate them into your files and solutions, but you will also understand what you are doing and be able to create custom Ribbons tailored to your needs.
A Customization Example for Pre-2007 UIs In order to give you an idea of what is going to happen to your old customization, we will work through an example. This customization has a reasonable amount of complexity so that you can have a clearer picture of what to expect. Don’t worry if you don’t understand how it works at this point; it will become clear as you work through the examples in the rest of the book. (This example is applicable to Excel. You can find the code on the book’s website at www.wiley.com/go/ribbonx.) In Figure 1-5, the menu is dynamic. When an option from the list is selected, another list is created for that particular option. In addition, if other workbooks are opened or created in the same Excel session, these custom options are no longer available (the options are disabled). They’re available only for this specific workbook and adapt according to which worksheet is currently active.
Figure 1-5: How old customization is built into the new UI
We describe the entire VBA process for this example so that you can have a look at how things would work on a relatively complex customization. If you are not fluent
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 11
■
An Introduction to the Office User Interface
with VBA, there is no need to panic. Chapters 4, 5, and 12 delve into the relevant aspects of VBA and explain things in sufficient detail for you to understand and be confident in creating customizations. For now, run this example and see how Excel 2007 handles a customization code written for Excel 2003. This will help you understand some of the key differences between the old way and the new way of doing things and it will make it easier for you to visualize what we are working with as we progress in this book. We describe below the steps you need to follow in order to implement this solution. Keep in mind that you aren’t expected to complete this exercise at this time. Rather, it is intended to give you the big picture and show you where you’re headed. You might want to start by seeing what we’re creating. Just download this chapter’s files and open Customization Old Style.xlsm. As you go through the book, you will learn about each of the steps to create custom add-ins as well as some VBA fundamentals, including how to create and work with class modules. For now, it is probably easiest to just review the code in our files. Alternatively, if you feel you want to try it yourself, you can copy the code from the referenced Excel file (or from the chapter’s text file) and paste it in as instructed. First, open ThisWorkbook and add the following code to the code window. (You do so by pressing Alt+F11 to open the VBE (Visual Basic Editor). From there, double-click on the ThisWorkbook object to open its code window.) Private Sub Workbook_BeforeClose(Cancel As Boolean) Call wsDeleteMenus End Sub Private Sub Workbook_Open() Call wsBuildMenus End Sub
Next, add a class module to your project. This is done from the Insert menu in the VBE window (Insert ➪ Class Module).
N OT E Remember that this is just an example to give you the big picture about what we’ll be covering and why. At this point, you aren’t expected to understand everything or to be able to interpret all the code, but by the time you finish this book, you will be proficient with these customizations and all of their intricacies.
C R O S S-R E F E R E N C E Chapter 12 defines and shows you how to work with class modules.
You can name the following class module whatever you like, but in this example we use clsEvents, so be sure to refer to the correct module in your own code. Public WithEvents appXL As Application Public WithEvents drop As Office.CommandBarComboBox
11
91118c01.qxd:WileyRedTight
12
Part I
■
11/28/07
7:33 PM
Page 12
The Building Blocks for a Successful Customization
Private Dim Dim Dim
Sub appXL_SheetActivate(ByVal Sh As Object) ws As Worksheet wb As Workbook i As Long
On Error GoTo Err_Handler Set wb = ActiveWorkbook If Not wb.Name = ThisWorkbook.Name Then Exit Sub Set g_cmdbarcboBox = g_cmdBar.FindControl _ (Type:=msoControlDropdown, Tag:=”myList”) g_cmdbarcboBox.Clear For Each ws In Sh.Parent.Sheets g_cmdbarcboBox.AddItem ws.Name Next For i = 1 To g_cmdbarcboBox.ListCount If g_cmdbarcboBox.List(i) = Sh.Name Then _ g_cmdbarcboBox.ListIndex = i: Exit For Next Call drop_Change(g_cmdbarcboBox) Exit Sub Err_Handler: ErrHandle Err End Sub Private Sub appXL_WorkbookActivate(ByVal wb As Workbook) Set g_cmdbarcboBox = g_cmdBar.FindControl _ (Type:=msoControlDropdown, Tag:=”myList”) If wb.Name = ThisWorkbook.Name Then g_cmdbarcboBox.Enabled = True appXL_SheetActivate wb.ActiveSheet Else: Call deleleteControls g_cmdbarcboBox.Enabled = False End If Exit Sub Err_Handler: ErrHandle Err End Sub Public Sub setDrop(box As Office.CommandBarComboBox) Set drop = box End Sub Private Sub drop_Change(ByVal Ctrl As Office.CommandBarComboBox)
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 13
■
An Introduction to the Office User Interface
Select Case UCase(Ctrl.Text) Case “SUPPLIERS” Call setMNUSUPPLIERS Case “CUSTOMERS” Call setMNUCUSTOMERS Case “ACCOUNTS” Call setMNUACCOUNTS Case Else Call deleleteControls End Select End Sub
Finally, add a standard module (in the VBE window select Insert ➪ Module) where we will insert the following code: Public Public Public Public
Const Const Const Const
gcstrCMDBARNAME gcstrMNUSUPPLIERS gcstrMNUCUSTOMERS gcstrMNUACCOUNTS
Public Public Public Public Public Public
g_cmdBar g_cmdbarMenu g_cmdbarBtn g_cmdbarcboBox gcls_appExcel gcls_cboBox
As As As As
String String String String
= = = =
“DYNAMIC MENU” “Suppliers” “Customers” “Accounts”
As As As As As As
CommandBar CommandBarPopup CommandBarButton CommandBarComboBox New clsEvents New clsEvents
Sub wsBuildMenus() Call wsDeleteMenus On Error GoTo Err_Handler Set g_cmdBar = CommandBars.Add(gcstrCMDBARNAME, msoBarFloating) g_cmdBar.Width = 150 Set g_cmdbarcboBox = g_cmdBar.Controls.Add(Type:=msoControlDropdown) With g_cmdbarcboBox .Tag = “myList” .OnAction = “selectedSheet” .Width = 150 End With Set g_cmdbarBtn = g_cmdBar.Controls.Add(Type:=msoControlButton) With g_cmdbarBtn .Caption = “Help” .OnAction = “runHelp” .Style = msoButtonIconAndCaption .FaceId = 984 End With
13
91118c01.qxd:WileyRedTight
14
Part I
■
11/28/07
7:33 PM
Page 14
The Building Blocks for a Successful Customization
Set gcls_appExcel.appXL = Application gcls_cboBox.setDrop g_cmdbarcboBox With g_cmdBar .Visible = True .Protection = msoBarNoChangeDock + msoBarNoResize End With Exit Sub Err_Handler: ErrHandle Err End Sub Sub wsDeleteMenus() On Error Resume Next Application.CommandBars(gcstrCMDBARNAME).Delete Set Set Set Set Set Set End Sub
g_cmdBar = Nothing g_cmdbarMenu = Nothing g_cmdbarBtn = Nothing g_cmdbarcboBox = Nothing gcls_appExcel = Nothing gcls_cboBox = Nothing
Sub deleleteControls() On Error Resume Next g_cmdBar.Controls(gcstrMNUACCOUNTS).Delete g_cmdBar.Controls(gcstrMNUCUSTOMERS).Delete g_cmdBar.Controls(gcstrMNUSUPPLIERS).Delete End Sub Sub selectedSheet() Dim g_cmdbarcboBox As CommandBarComboBox On Error Resume Next Set g_cmdbarcboBox = _ CommandBars.FindControl(Type:=msoControlDropdown, Tag:=”myList”) ActiveWorkbook.Sheets(g_cmdbarcboBox.Text).Activate End Sub Sub setMNUACCOUNTS() Call deleleteControls On Error GoTo Err_Handler Set g_cmdbarMenu = _ g_cmdBar.Controls.Add(Type:=msoControlPopup, BEFORE:=2) g_cmdbarMenu.Caption = gcstrMNUACCOUNTS Set g_cmdbarBtn = g_cmdbarMenu.Controls.Add(Type:=msoControlButton) g_cmdbarBtn.Caption = “New Account”
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 15
■
An Introduction to the Office User Interface
Set g_cmdbarBtn = g_cmdbarMenu.Controls.Add(Type:=msoControlButton) g_cmdbarBtn.Caption = “Delete account” Exit Sub Err_Handler: ErrHandle Err End Sub Sub setMNUSUPPLIERS() Call deleleteControls On Error GoTo Err_Handler Set g_cmdbarMenu = _ g_cmdBar.Controls.Add(Type:=msoControlPopup, BEFORE:=2) g_cmdbarMenu.Caption = gcstrMNUSUPPLIERS Set g_cmdbarBtn = g_cmdbarMenu.Controls.Add(Type:=msoControlButton) g_cmdbarBtn.Caption = “New Supplier” Set g_cmdbarBtn = g_cmdbarMenu.Controls.Add(Type:=msoControlButton) g_cmdbarBtn.Caption = “Current data” Exit Sub Err_Handler: ErrHandle Err End Sub Sub setMNUCUSTOMERS() Call deleleteControls On Error GoTo Err_Handler Set g_cmdbarMenu = _ g_cmdBar.Controls.Add(Type:=msoControlPopup, BEFORE:=2) g_cmdbarMenu.Caption = gcstrMNUCUSTOMERS Set g_cmdbarBtn = g_cmdbarMenu.Controls.Add(Type:=msoControlButton) g_cmdbarBtn.Caption = “New Customer” Set g_cmdbarBtn = g_cmdbarMenu.Controls.Add(Type:=msoControlButton) g_cmdbarBtn.Caption = “Outstanding pmts” Exit Sub Err_Handler: ErrHandle Err End Sub Sub ErrHandle(ByVal objError As ErrObject) MsgBox objError.Description, vbCritical, objError.Number Call wsDeleteMenus End Sub
You can now run the preceding code and see for yourself how it behaves in relation to the new UI. Start by selecting the different worksheet tabs and then open/add new workbooks to your working environment.
15
91118c01.qxd:WileyRedTight
16
Part I
■
11/28/07
7:33 PM
Page 16
The Building Blocks for a Successful Customization
Ribbon Components This section provides a guided tour of the Ribbon components. This will help you become familiar with the structure so that things will run more smoothly when you begin XML coding of the UI. The user interface in Office 2007 is made up of a number of components (or elements, if you prefer) that determine how you interact with the application, much in the same way that the hierarchical approach had its own elements. These components are listed in Table 1-1 and Table 1-2, and illustrated in Figure 1-6. (The numbers in the tables correspond to the callout numbers in Figure 1-6.) As previously mentioned, the idea is that this new UI will help users to get the job done quicker, which should translate into higher productivity. However, before productivity increases, you need to familiarize yourself with these elements, especially if your objective is to customize the Ribbon. The task of customizing the Ribbon will be a whole lot easier once you understand the underlying logic of the XML code. Table 1-1: The Three Basic Ribbon Components COMPONENT
WHAT IT IS FOR
1. Tab
Each tab brings together core tasks that you perform. Tabs bring together all these related tasks.
2. Group
In the same way that tabs bring together related tasks, groups bring together related commands.
3. Command
A command represents an action that you want to perform. It can appear packed in different forms, such as buttons, galleries, menus, edit boxes, etc.
1
4
5
2
3
Figure 1-6: Ribbon components viewed from an Access perspective
Figure 1-6 shows a total of five components (or elements). Numbers 1, 2, and 3 are “basic” components; numbers 4 and 5 are not.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 17
■
An Introduction to the Office User Interface
Table 1-2: The Two Non-Basic Components of the Ribbon COMPONENT
WHAT IT IS FOR
4. Quick Access Toolbar (better known as QAT)
A toolbar for adding commands that you want just a click away. The QAT can take shared or document controls.
5. Office Menu (also known as Office Button)
A menu containing file-related tasks such as printing, file properties, saving, etc.
These elements are related in a hierarchical construct within the XML code. This construct (or concept) serves to encapsulate each control within its parent element. The way this structure works will become clear when we start to analyze the tags used to build customization.
N OT E When adding controls to the QAT such as buttons and groups, you can choose between a document control and a shared control. A document control, as the name suggests, is a control that belongs to the document itself and cannot be seen or accessed by another document that might be opened in the same session. Conversely, a shared control is available to all documents opened in the same session or any other session.
Tips for Navigating the Ribbon and Quick Access Toolbar (QAT) This section discusses some tips for navigating the Ribbon and the Quick Access Toolbar. Although these tips relate to the default Ribbon, you should keep them in mind for your customization — especially the keytips accessible through the keyboard, as these can be changed through XML code and will work with your own customization as well as built-in tabs and controls.
Using Keyboard Shortcuts and Keytips We start off with keyboard shortcuts and keytips. This is, simply put, the easiest, quickest, and generally most efficient way to access commands in any application. It involves pressing a combination of keys that will perform some kind of action, such as copying, cutting, pasting, or navigating through some of the Ribbon’s elements. If you have used keyboard shortcuts before, you should be happy to know that old shortcuts still work fine with the new UI (in fact, they work in exactly the same way as before). Hence, if you need to copy something, you can still use Ctrl+c; similarly, if you wish to paste, you can continue to use Ctrl+v. A shortcut requires that you press one key plus another.
17
91118c01.qxd:WileyRedTight
18
Part I
■
11/28/07
7:33 PM
Page 18
The Building Blocks for a Successful Customization
You will remember that for previous versions of Office, you could press the Alt key to activate the top-level menu (referring to the Accelerator key). You would then use other keys (which were always underlined, indicating which key you should press next) to get to the command you wanted. Office 2007 introduces an extension of the Accelerator idea from previous versions of Office: keytips. Keytips are also accessed by pressing and releasing the Alt key (if you press and hold long enough it will show the keytips as well, as shown in Figure 1-7).
Figure 1-7: Keytips for an Access project
Next, all you have to do is press the corresponding key (or combination of keys) shown in the Ribbon. This is slightly different from the shortcut approach, because with keytips you first press one key and then you press another.
N OT E Notice that some keytips are dimmed. When a keytip is dimmed it is not accessible in the current context (for example, undo will not be functional until there is something that can be undone). When you press the keytip key and it refers to a tab, you automatically move into that tab context. This time around, you do not need to press Alt to reveal the keytips for the controls. As shown in Figure 1-8, all the keytips are automatically revealed to you.
Figure 1-8: Keytips for controls under the Create tab in Access
Now, if the next keytip leads you to a drop-down or menu, these will be extended to show the keytips for any controls that may have been placed in them. The greatest single advantage of the keytip is that it is an attribute available in almost all controls you find in the Ribbon. Compare this with the limited scope in previous versions of Office and you start to appreciate its beauty.
N OT E Keytips are available in almost all controls, but some controls (such as the menuSeparator and labelControl) are not really command controls and therefore have no keytips. These are auxiliary controls and do not perform any kind of action.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 19
■
An Introduction to the Office User Interface
Another benefit is that you can still use the Accelerator keys in the same manner as you could in previous versions of Office. Suppose you want to open the Insert Function dialog box in Excel. Using keytips, you could press Alt+M+F. An alternative was to use the Accelerator route in Office 2003, which just entailed pressing Alt+I+F. Now, Excel 2007 keeps track of the keys you press to open the Insert Function dialog box, so it will recognize the key combinations that were used in Office 2003. Figure 1-9 shows the Accelerator sequence in Office 2007 for Alt+I+F from Office 2003.
Figure 1-9: Office 2007 will recognize and respond to the Accelerator sequences used in Office 2003.
The Accelerator access mode is activated whenever you combine keys that made up the Accelerator route in Office 2003.
T I P You can also press the F10 function key to bring up the keytips and start an Accelerator sequence.
Using the Mouse Wheel If you have your Ribbon maximized, you can navigate the tabs using the mouse wheel. Just place the mouse pointer on top of the Ribbon and spin the wheel. If you scroll towards you, the Ribbon will scroll along the tabs from left to right. Conversely, if you scroll away from you, it will scroll from right to left along the tabs until it reaches the first tab on the left.
Minimizing and Maximizing the Ribbon If you feel the Ribbon is taking up too much space at the top of your screen you can minimize it, as shown in Figure 1-10. Chevron icon
Figure 1-10: Minimized Ribbon
19
91118c01.qxd:WileyRedTight
20
Part I
■
11/28/07
7:33 PM
Page 20
The Building Blocks for a Successful Customization
To minimize or maximize the Ribbon, you can do one of the following: ■■
Double-click on any tab.
■■
Click on the chevron-like icon and toggle Minimize the Ribbon button.
■■
Right-click anywhere on the QAT or tab strip, or on any group’s description and choose Minimize Ribbon.
Once the Ribbon is minimized you will not be able to navigate the tabs using the mouse wheel, and clicking on the tabs will only temporarily expand the Ribbon content for the selected tab. However, while the Ribbon has the focus, you will still be able to choose other tabs and view the commands within any group under the chosen tab. To expand the Ribbon back to the default display, merely follow one of the steps listed for minimizing the Ribbon. Essentially, they work like a toggle to minimize/ maximize on demand.
Adding Commands to the QAT If you use some controls more often than others (such as open/close), you can place them on the Quick Access Toolbar (QAT) so that you have just that — quick access. The QAT is the nearest you will get to toolbar-style customization. It works in pretty much the same way that toolbars did in the past, by enabling you to place controls on a specific toolbar for ease of access. Compared to what was previously possible, the QAT’s scope is rather limited, but removing unwieldy customizations, such as the docking capability, also eliminated the complexities and anxieties of managing the old toolbar. It is remarkably easy to manually add commands to the QAT. Just use any one of the following processes: ■■
Click on the chevron-like icon, and then select More Commands.
■■
Click the Office Menu and select (Application) Options ➪ Customize.
■■
Right-click anywhere on the QAT or tab strip, or on any group’s description, and select Customize Quick Access Toolbar. When the Customize Quick Access Toolbar options window is open, as shown in Figure 1-11, simply select the commands you want to add to the QAT and click Add.
T I P To quickly add commands, right-click the command (Ribbon or Office Menu) and choose Add to Quick Access Toolbar. Note that under the Customize Quick Access Toolbar drop-down list, you will find your XLSX, DOCX, ACCDB, and so on, files. This means the command you’re adding to the QAT can be specific to the file or can be shared across all other open files. By default, the customization is shared (for all files opened). If you want it to be file-specific, then the icon will change to show this subtle difference, as shown in Figure 1-12.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 21
■
An Introduction to the Office User Interface
Figure 1-11: Adding commands to the QAT in Word
Document control
Shared controls
Figure 1-12: Icons with a frame refer to document controls.
The little frame around the icon indicates that it is a document control rather than a shared control. A shared control is available to all opened documents, whereas a document control is only available to the document to which it was added. It all may look great to begin with, but as you might quickly realize, it is very easy to burden the QAT with too many buttons; and even then there will be more commands that you’d like to add. Thankfully, the QAT is quite versatile; in addition to being able to add buttons, you can also add groups. The process is very simple and can be accomplished using one of the following approaches: ■■
Open the application options. Under Choose Commands From, select the tab that contains the group you are looking for. In the list, you will notice that some have an icon with an arrow pointing down. This indicates it is a group.
■■
Right-click on the group name tab and then click on Add to Quick Access Toolbar.
Figure 1-13 shows the QAT in an Access project. In addition to buttons, this has the Database Tools group.
21
91118c01.qxd:WileyRedTight
22
Part I
■
11/28/07
7:33 PM
Page 22
The Building Blocks for a Successful Customization
Figure 1-13: Adding groups to the QAT
If you are wondering whether this applies to your custom groups, yes, it does apply to custom groups.
T I P Notice the icons! That’s right, you should look at the icons in the Customize options because they indicate what type of object is being added. An arrow pointing down indicates a group, an arrow pointing right indicates a gallery, and a vertical bar with an arrow pointing right indicates a splitButton.
C R O S S-R E F E R E N C E See Chapter 14 to learn more about Quick Access Toolbar customization using XML.
Assigning a Macro to a QAT Button If you plan to customize the Quick Access Toolbar, probably the best way is to use XML code. The problem with this, however, is that such customization can only happen if you build the entire Ribbon from scratch. This is covered in detail in Chapter 14. If you need to add a button to the QAT for which you want to attach certain functionality, you can easily make such a customization using the following steps (see Figure 1-14 for a walk-through): 1. Go to Office Menu ➪ (Application) Options (alternatively, you can use the chevron-like QAT icon to access More Commands). 2. Click Customize to enter the Customize the Quick Access Toolbar customization window. 3. From the drop-down Choose Commands From, select Macros to show a list of available macros. 4. From the Customize Quick Access Toolbar drop-down, select the option you want (such as the Current document or For all documents (default) option). 5. Select your macro from the list and click Add. 6. Click OK when you’re done.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 23
■
An Introduction to the Office User Interface
Figure 1-14: Assigning a macro to the QAT
You’re probably now wondering what is up with the icon assigned to the HelloWorld macro. That is definitely an ugly icon and one that you would certainly want to replace, which conveniently brings us to another step in the process. To change an icon, click the Modify button to display a list of the available icons (shown in Figure 1-15).
Figure 1-15: Choosing a different icon for the macro button
Look through the options and choose an icon (or symbol, if you prefer the dialog box nomenclature) and when you’ve made your selection, click OK to continue. While you are at it (and before you click OK), you can also change the Display name. The Display name is the tip that will appear onscreen when the mouse pointer hovers over the button, as shown in Figure 1-16.
23
91118c01.qxd:WileyRedTight
24
Part I
■
11/28/07
7:33 PM
Page 24
The Building Blocks for a Successful Customization
Figure 1-16: Display name for the macro button
Changing the QAT Location By default the QAT is located above the Ribbon. You can change its location by either of the following methods: ■■
Click the chevron-like icon and select Show Below the Ribbon.
■■
Right-click anywhere on the QAT or tab strip, or on any group’s description and select Customize Quick Access Toolbar.
Preparing for Ribbon Customization Developing Ribbon customizations involves several steps. The first stage works mostly with structure and therefore involves XML; the second stage is functionality-related, which involves VBA code (or some other programming language such as C#). Happily, there are some fundamental components that are common to most customizations, so you can create it once and then use it as the foundation for future projects. The following sections describe some important preparations that will save you valuable time throughout the customization process.
Showing the Developer Tab As you work with VBA in this book, you may find it useful to have the Developer tab active. The tab itself is not critical, as the keyboard shortcuts still exist and can be used to access macro features for any work that you will do; but it will be useful to have the development tools at hand, especially if you prefer using the mouse rather than keyboard shortcuts. We recommend having the Developer tab active because after you have finished your XML code you use VBA to add functionality to the UI, so it is convenient to have the Developer’s tools at hand when you need them. Furthermore, the Developer’s tools also provide an XML group, which is useful when inspecting the XML schema for the Ribbon UI. As shown in Figure 1-17, the Developer tab appears as the last tab in the standard Ribbon.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 25
■
An Introduction to the Office User Interface
Figure 1-17: The Developer tab is useful when it’s time to add VBA functionality to your customization.
To enable the Developer tab, follow these steps (as illustrated in Figure 1-18): 1. Click on the Office logo. 2. Select (Application) Options. 3. Select Popular and choose the option Show Developer tab in the Ribbon.
Figure 1-18: Enabling the Developer tab
These instructions and illustrations are identical for Excel and Word. In Access, however, you use the Create tab to access macro and VBA objects. Another difference in Access is that it does not have certain features, such as macro recording. Recording macros is a very handy feature that helps us to discover important information about objects. Nevertheless, you may find that recording macros in Excel/Word can actually help you figure out information and processes that are applicable to Access.
C R O S S-R E F E R E N C E See Chapter 4 for instructions on recording macros and for programming techniques that are used in this book and that you can apply to your daily work.
25
91118c01.qxd:WileyRedTight
26
Part I
■
11/28/07
7:33 PM
Page 26
The Building Blocks for a Successful Customization
Showing CustomUI Errors at Load Time Before loading the UI into Excel, Access, or Word, you will always want to check your XML consistency. To help with this, a few tools are discussed in Chapter 2. Despite all good intentions, however, the UI may pass the test in these tools but fail when it loads. Therefore, it is important to be familiar with some of the factors that might cause the loading failure. First, although each of these applications shares very similar XML code, they also contain controls that are unique to that application. Therefore, if code including an application-unique control were moved to a different application, the code would pass the test on the tool but fail on load. Second, you may also have less obvious problems. For example, you could assign an idMso (we’ll discuss these more in Chapter 3) to a button when it actually belongs to a toggleButton. Again, these may pass the first check, but will fail on load. To ensure that consistency is also checked and that errors are displayed when the CustomUI is loaded, you can use the following process: 1. Click Office Menu ➪ (Application) Options. 2. Select the Advance option and scroll to the General group. 3. Select the Show add-in user interface errors option. Once you’ve made the selection, click OK to continue. The location for this option is the same for Excel, Access, and Word. As shown in Figure 1-19, when the document is loaded, if it contains an error it is immediately reported, indicating the line and column in which the error is located (including the file location).
Figure 1-19: Reporting error when loading UI
In this case, an error has occurred because the UI is being loaded in Word but it contains an attribute value that is specific to Excel (ConditionalFormatting).
Reviewing Office 2007 Security If this is your first time working with Office, you may want to take a few minutes to review Chapter 17. That chapter contains information about how to set up the Trust Center, macro security, and digital signatures so that you aren’t constantly being harassed about potentially dangerous files while you are developing. Making these changes now will save you some time and frustration as you go through the exercises in this book.
91118c01.qxd:WileyRedTight
11/28/07
7:33 PM
Chapter 1
Page 27
■
An Introduction to the Office User Interface
Has Customization of the Office UI Become Easier? Our objective throughout this book is to modify and customize the work area (user interface). Unfortunately, the task is relatively arduous with the new UI for Microsoft Office as compared to previous versions. Moreover, the challenges are compounded by the small differences between the applications, which means that we cannot rely 100% on the XML code to be exchangeable and applicable across different Office applications. Figure 1-20 shows an example of a Ribbon customization. This has an added tab and a group containing a few command buttons. And, of course, it includes an action that will be executed when the command button is clicked.
Figure 1-20: Customizing the Ribbon
If you previously created a lot of custom menus and toolbars (whether through VBA or not), you will probably have a hard time accepting the new UI. However, once you get past that first encounter and resistance to change, things should start looking a bit rosier. The biggest challenge with respect to the new UI is the XML code it contains; and a custom UI can require more lines of code than you might currently imagine. Therefore, for now, accept the guidance that a lot of planning is required prior to writing the first line of XML for your new UI. Later, a few hundred lines into your code, you do not want to be trying to find and fix something. Furthermore, XML is case sensitive, so writing an attribute such as getVisible as getvisible will cause a syntax error in the structure of the XML code. In addition, now we need to create and work with callbacks. These take the arguments from the Ribbon objects. Fortunately, there’s the Custom UI Editor that can read through your code and generate the callbacks for any attribute that requires one. All of this might sound intimidating now, but by the time you finish this book, you will be sailing through the Ribbon customization process. Obviously, this book cannot contain everything you might possibly come across or want to know about, but it contains everything that you need to know for most common scenarios and it will help you avoid a whole lot of pain during the learning process.
Conclusion In explaining how the Ribbon came to be, we provided a little background and history of the Microsoft Office UI. Now you can understand why it was necessary for a paradigm change after so many years of relying on hierarchical menus. As with any change this dramatic, there can be a steep learning curve, but the benefits become evident very quickly.
27
91118c01.qxd:WileyRedTight
28
Part I
■
11/28/07
7:33 PM
Page 28
The Building Blocks for a Successful Customization
In this chapter you learned about the Quick Access Toolbar, shortcuts, and keytips. You learned some basic customizations that can be done without any programming and how those customizations can be incorporated into your UI as a document or as a shared customization. Although lengthy, and maybe a bit intimidating at this stage, you also saw a sample of an old customization style so that you could try it for yourself. It is hoped that being able to contrast the new with the old has added to your appreciation for what you will soon be able to do. In Chapter 2 we cover access to the UI customization layer, and from there on we introduce the tools you need to successfully customize the new Office user interface. Have fun.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 29
CHAPTER
2 Accessing the UI Customization Layer
Before you can start exploring how to customize the Ribbon to get it the way you like it, you need to know how and where to place the code to do so. This chapter leads you there by discussing the file formats in Office 2007, and shows you how to crack your files open for customization. The first section, “Accessing the Excel and Word Ribbon Customization Layers,” discusses Excel and Word files, and begins by describing the fundamentals behind how the new Office 2007 files are structured. You learn how to access the Ribbon customization layer and store the required code. Once you’ve mastered this, you learn about a couple of handy tools you can use to make it much easier to set up, program, and debug Ribbon customizations. Rest assured that at this stage, you’ll be provided with all the code you need, so don’t worry about getting bogged down in learning to write it just yet. The “Microsoft Access Customizations” section dives into storing RibbonX customizations in Microsoft Access, as this is very different from working with the other Office applications. A few different methods can be used to include Ribbon customizations in Access, and a brief introduction to each of them is covered. Again, all code is provided for you at this stage. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
N OT E This book provides processes and code for customizing the Ribbon in Excel, Access, and Word. Microsoft also introduced the Ribbon, and new file formats, for several other Office applications, including PowerPoint, Outlook forms, OneNote, Visio, and InfoPath. 29
91118c02.qxd:WileyRedTight
30
Part I
■
11/28/07
9:14 PM
Page 30
The Building Blocks for a Successful Customization
Accessing the Excel and Word Ribbon Customization Layers This section explains how to create Ribbon modifications using only the software that ships natively with Windows. It also explains how to accomplish the customizations using some third-party tools.
What’s New in Excel and Word Files? In versions of Microsoft Office up to 2003, most documents were stored in binary files, but this changed with the arrival of Microsoft Office 2007. For the release of this version of Office, Microsoft adopted the OpenXML file format as their new standard. While it’s pretty obvious that the file formats have changed, what may not be obvious is that, unlike files in previous Office versions, the new files are actually contained in a zipped XML format! This is important for a few reasons: ■■
We can unlock and modify the underlying XML files quite easily with Notepad. (No third-party tools are required, as Windows XP and later can work with compressed, or zipped, files.)
■■
We can use a similar data structure across different applications to acquire like results. From Excel to Word, and even in PowerPoint, customizing the new Office 2007 user interface follows exactly the same methods.
■■
Generally, files stored in the new file formats are compressed, and take up much less space than their old binary counterparts.
Creating a Ribbon Customization with Notepad Enough talk about what can be done; it’s time to actually play with a customization. For the purposes of this example, you create a very simple Ribbon customization that adds a new tab to the Ribbon and places a couple of Microsoft’s built-in groups on this tab.
N OT E While this example demonstrates the method to complete a Ribbon customization in Excel, you can use the same steps to accomplish this in Word as well. You simply open Word instead, and modify the docx file instead of the xlsx file, as demonstrated in a subsequent example.
Creating the customUI File The first thing to do is create the file that will hold the XML used to modify the Ribbon. To do this, create a folder on your desktop and rename it customUI.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 31
Chapter 2
■
Accessing the UI Customization Layer
T I P Before continuing, make sure that your system is not set to hide known file extensions, as this will get in the way in the next step. Showing extensions won’t cause any security issues and, in fact, it can sometimes help prevent them. To show extensions, open your folder view and choose Organize ➪ Folder and Search Options (Tools ➪ Folder Options in Windows XP). On the View tab, scroll down and uncheck the box that says “Hide extensions for known file types” and then click OK. Now open the customUI folder that you just made and create a new text file. When prompted for a save name, call it customUI.xml. Right-click it, choose Edit, place the following code inside, and save the file:
N OT E This code has been validated and checked for you, so it will work as long as you type it correctly. The leading spaces on each line are to provide clarity, rather than functionality, so they do not affect code performance. However, the code is completely case sensitive, so typing the incorrect case will inevitably cause the code to fail.
T I P Rather than type code, you can copy the code from this chapter’s companion files. The files for each chapter are available on the book’s website.
Creating the File to Use the Customized UI Next, create the Excel file that will implement the customization. This will start out as a typical Excel file, but that won’t last long because you will attach your customUI.xml file to it. Naturally, you’ll need to use one of the new Office 2007 OpenXML file formats. To do this: 1. Create a folder named “Test” on your Desktop.
31
91118c02.qxd:WileyRedTight
32
Part I
■
11/28/07
9:14 PM
Page 32
The Building Blocks for a Successful Customization
2. Open Excel. 3. Create a new workbook if a blank one is not already showing. 4. Save the workbook to your desktop as a 2007 Excel workbook (.xlsx format). As shown in Figure 2-1, for the purposes of this example the file is called MyFirstUIModification.xlsx.
Figure 2-1: Saving an .xlsx file
Attaching the XML to the File You are now at the stage where you can attach the XML file you created to the workbook itself. To do this, you need to jump through a couple of hoops. As mentioned earlier in this chapter, all OpenXML files are zip containers; and in order for the RibbonX code to be read, it needs to be inside that zip container. Thankfully, this is easier than it sounds.
N OT E All customizations to the user interface are written in XML, which modifies the RibbonX structure within Office. These two terms are used interchangeably throughout this book.
Find the file you created (MyFirstUIModification.xlsx), right-click the file name, and choose Rename. Leave the entire filename, including the file type extension, intact, but add .zip at the end. The file should now be MyFirstUIModification.xlsx.zip. You are changing the filename extension, so make sure you select Yes in response to the warning about the potential consequences of your actions, as shown in Figure 2-2!
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 33
Chapter 2
■
Accessing the UI Customization Layer
Figure 2-2: Modifying a file’s extension to expose its zipped structure
Notice that the file has now changed into a zip file, instead of just a standard Office document file.
N OT E Renaming the file causes the file to show a different icon, indicating that it is a zipped folder. Double-click the zipped file to open it, and you should see the result shown in Figure 2-3.
Figure 2-3: The contents of an OpenXML file
Now that you have the zipped file open, you need to move the customUI folder into it. The drag-and-drop approach works great for this.
33
91118c02.qxd:WileyRedTight
34
Part I
■
11/28/07
9:14 PM
Page 34
The Building Blocks for a Successful Customization
Next, link the UI modifications by editing the .rels file to indicate a relationship between the file and the customUI folder. To do this, drag the _rels folder to your desktop (or another location of your preference). Then, open the newly created _rels folder and use Notepad to edit the .rels file. With the .rels file open in Notepad, insert the new relationship immediately before the element at the end of the file. Specifically, you need to type in the following code, being very careful with punctuation, spaces, and capitalization. The code is case sensitive:
N OT E The code that you will be looking at in the .rels file is all on a single line when you begin, but it’s not at all necessary to keep it that way. To make it easier, scroll through the code and insert a hard return after every blank space and > character. At the end of the process, you’ll end up with code that looks like what is shown in Figure 2-4.
Figure 2-4: The contents of the .rels file
Finally, to finish the process, save your new .rels file and close Notepad. Return to the zip container and right-click the _rels folder and then delete it. You are now ready to drag your copied _rels folder back into the document’s zip container. Now that you’ve saved the .rels file and replaced the _rels folder, rename your workbook back to the xlsx filename that it started with. To do this, again right-click the file and choose Rename; then just knock the .zip off the end (so that you’re left with MyFirstUIModifcation.xlsx). The icon should return to the familiar Excel icon. Finally, open the file in Excel and take a look at what you’ve accomplished.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 35
Chapter 2
■
Accessing the UI Customization Layer
N OT E If you get the error shown in Figure 2-5, check your .rels and customUI.xml files. Something is written in the wrong case or spelled incorrectly!
If you do see an error like the following, select OK and return to the Custom UI editor to troubleshoot the XML code.
Figure 2-5: An error in the .rels or customUI.xml files
If you did everything right, a new tab called My Very Own Tab is inserted before the Home tab, which contains both the Font and Zoom groups, as show in Figure 2-6. If you try some of the commands, you’ll see that they function just the same as those from Microsoft’s built-in groups. Indeed they should, as they are just copies of the built-in groups placed on your new tab.
Figure 2-6: My Very Own Tab in Excel
Using the Microsoft Office 2007 Custom UI Editor to Modify Your UI Without question, using Notepad to set up your Custom UI is a little intimidating and a lot of work. It can be especially frustrating to deal with text wrapping. Fortunately, there is an easier way: using a program called the Microsoft Office 2007 Custom UI Editor. As this is such a mouthful, we’ll refer to it as the CustomUI Editor for the rest of the book. The CustomUI Editor is a handy little utility that makes editing your OpenXML files much easier than the horribly complex route detailed earlier. In addition, it provides some validation and other tools that reduce the stresses of development. Best of all, this utility is available as a free download!
35
91118c02.qxd:WileyRedTight
36
Part I
■
11/28/07
9:14 PM
Page 36
The Building Blocks for a Successful Customization
Installing Microsoft .NET Framework 2.0 for Windows XP Users Are you one of those users who has decided to hold off on installing Windows Vista for any reason? Well, if you’re still running on Windows XP, you need to know something before you can move on. We’re going to be installing an application to make our jobs easier, but it requires the Microsoft .NET Framework 2.0 to run. Vista users are ready to proceed because Vista ships with version 2.0 of the .NET framework. However, it is another story for computers with Windows XP. Therefore, before you go any further, it’s a good idea to ensure that you have the Microsoft .NET Framework 2.0 installed. The first check is to go to Start ➪ Control Panel ➪ Add or Remove Programs and check for Microsoft .NET Framework 2.0, as shown in Figure 2-7.
Figure 2-7: Checking for Microsoft .NET Framework 2.0 in Windows XP
If it isn’t there, go to Windows Update (or Microsoft Update if you’ve upgraded) to get the framework. (If you know for a fact that it is not installed, you can go straight there.) The Windows Update site can be reached at http://update.microsoft.com (see Figure 2-8). For reference, the Microsoft .NET Framework 2.0 is included under the “Software, Optional” category.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 37
Chapter 2
■
Accessing the UI Customization Layer
Figure 2-8: Microsoft .NET Framework 2.0 Windows Update
If Microsoft .NET Framework 2.0 isn’t in the list for Optional Software updates, it means that the framework has either been installed already and you should retrace the earlier steps, or it was hidden by someone who may have been applying updates in the past. If you suspect that the update was downloaded but hidden instead of installed, click the Restore Hidden Updates link under the Options section on the left, as the update might have been flagged to not be shown. Once you have downloaded and installed the .NET framework package, go back and check for updates. At the time of writing, there were already two High Priority updates for this product. While you’re there, you may want to apply any other critical updates that may have been missed on your system as well. Now that your Windows XP system is patched and ready, you may proceed to install the CustomUI editor.
N OT E You could skip the step of checking for the framework and Windows updates, and take your chances that the install would be successful. If the .NET Framework is not present, you will see the error shown in Figure 2-9, which will prompt you to obtain the update.
37
91118c02.qxd:WileyRedTight
38
Part I
■
11/28/07
9:14 PM
Page 38
The Building Blocks for a Successful Customization
Figure 2-9: .NET Framework missing dialog
Rather than just take your chances with the CustomUI editor installation, we recommend that that you utilize the earlier process and go through Windows Update to get the Microsoft .NET Framework 2.0. This ensures that you get the latest version direct from Microsoft, and it enables you to collect all the critical updates that follow. Keeping your updates current is an important part of computer health, so using Windows Update can kill the proverbial two birds with one stone.
Installing the Microsoft Office 2007 Custom UI Editor The CustomUI Editor can be downloaded free from http://openxmldeveloper.org/ articles/customuieditor.aspx. Once downloaded, extract the zip file and install it on your computer. Once the program is installed, open it. You’ll be staring at a blank screen. That’s not very exciting, so let’s open the file that we’ve been working on: MyFirstUIModification.xlsx. Suddenly, as shown in Figure 2-10, these files are a lot more interesting.
Figure 2-10: The CustomUI Editor in action
Compared to the plain bland text that we saw in Notepad, the code in the CustomUI Editor is quite colorful, rife with red, blue, and burgundy. Sure, it’s nice that it’s prettier; but you aren’t really interested in pretty code . . . or are you? The benefits of using this program will become much clearer as you proceed
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 39
Chapter 2
■
Accessing the UI Customization Layer
through this book; but suffice it to say that the colors help you read and interpret your code. The editor also enables you to easily package pictures, validate your code, store code snippets, and even generate the code framework required to react to callbacks. Ahh, a new term — callbacks are custom VBA routines that are fired when you click on your customized Ribbon controls. You’ll find that this becomes an indispensable tool in your kit for working with the Ribbon.
Using the CustomUI Editor to Customize the Ribbon Now that you have the CustomUI editor installed, it’s time to try another customization. Like the Notepad method, this will work with Excel or Word files equally well, so this time you’ll focus on a Word customization. To illustrate the difference in ease between using the two approaches, you’ll use the exact same code, but apply it to a Word document through the CustomUI Editor interface. To start the process, open Word, create a new document, and save it as a .docx file type. Close Word and open the CustomUI Editor. Open the Word file that you just saved through the Custom UI Editor. Enter the code that we used in the previous example.
T I P You can either meticulously type the code again or merely copy it from the workbook that you previously created or from the chapter download file.
Once you have all of your code entered, it is a very good idea to check the validity of your XML code. This step can alert you to many issues that you might not be able to see at first glance, and it can save you a huge amount of guessing when your modifications just don’t show up later. To check the validity, click the second toolbar button from the far right — the one with the check mark on it (shown in Figure 2-10). The tool will evaluate the code and report any errors that it might encounter along the way. What you are hoping to see is the message shown in Figure 2-11.
Figure 2-11: Validity check for the Custom UI XML
Ahh, isn’t it nice when things are “well formed?” If, on the other hand, you get a message like the one shown in Figure 2-12, there is at least one issue in your code.
39
91118c02.qxd:WileyRedTight
40
Part I
■
11/28/07
9:14 PM
Page 40
The Building Blocks for a Successful Customization
Figure 2-12: Errors in XML validation
At this point, it becomes a hunting game to track down any errors. Careful reading of the error message can help guide you toward a solution. In the case of the message shown in Figure 2-12, the “idmso” attribute has been spelled incorrectly; it should be idMso. You’ll recall that we emphasized the importance of spaces, punctuation, and case. RibbonX code is indeed case sensitive, and is written in what we call CamelCase (uppercase “humps” in the middle of the terms.) Once you have received the “well formed” message, that’s it! You’re good to go without making any modifications to the .rels file, as the editor takes care of those issues for you! Just hit the Save button and the custom UI is immediately attached to your file. Close the file in the CustomUI Editor and open your new document in Word. Your modification should be present, as shown in Figure 2-13.
Figure 2-13: “My Very Own Tab” in Word
Note here that despite using the exact same code in both the Excel and Word files, the options on the groups are slightly different. This is because each program has its own way of setting up groups with the applicable commands, but some of the groups share common names across multiple applications.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 41
Chapter 2
■
Accessing the UI Customization Layer
Storing Customization Templates in the CustomUI Editor You may encounter scenarios in which you want to refer to a custom UI that you previously developed. Again, you can use the CustomUI Editor to store and access custom templates, as shown in Figure 2-14.
Figure 2-14: CustomUI code templates
To demonstrate the ease and benefits of setting up your own templates, use Notepad to create a new text file on your computer and enter the following code into it:
Save the file in the folder Program Files\CustomUI Editor\Samples with the filename RibbonBase.xml.
N OT E The Program
Files\CustomUI Editor\Samples path assumes that you used the default installation path for the Microsoft Office 2007 Custom UI Editor. If you installed the program to a different path, you will need to modify the preceding instruction accordingly.
T I P If you cannot access this file in Windows Vista, it is due to the User Access Control feature restricting your permissions. You will need administrative rights in order to store these templates in the specified folder. Finally, open the CustomUI Editor and select the Samples menu. If your new file is in the right place, you should now see the RibbonBase entry on the list. Click it and voilà! There is your XML template, which you can use to start all future Ribbon customizations!
41
91118c02.qxd:WileyRedTight
42
Part I
■
11/28/07
9:14 PM
Page 42
The Building Blocks for a Successful Customization
Now that you have this on your menu, how do you use it? It’s quite simple, actually. Just create your new file, open it in the CustomUI Editor, and select your RibbonBase from the menu. It copies all of the code into your file for you, and you’re good to go! What a great way to get a head start.
C AU T I O N If you open a file that has customizations in place already and select your template, all of your current customizations will be overwritten with the template’s code. You can, of course, close the file without saving and not lose your current work.
Some Notes About Using the CustomUI Editor The CustomUI Editor is a fantastic utility that makes editing XML code much easier. As with all programs, however, it is not perfect. You should be aware of some CustomUI Editor “gotchas” before you dive right in: ■■
The CustomUI Editor does check the form of your XML tags, as well as make sure that you only use attributes that are defined in the XML schema. What it does not do, however, is check that you have provided valid attributes within the quotes. (You’ll learn about attributes later, but be aware that you can still receive errors even if your XML is well formed.)
■■
While writing and debugging your RibbonX code, it is very tempting to open your file in both the application and the CustomUI Editor at the same time. Trying to save the file in the CustomUI Editor while the file is open in the Office application will result in an error. In addition, even if you close the document that you are editing in the Office application and then save it in the CustomUI Editor, the editor will overwrite any changes that you made while editing your document in the application! It is much safer to close the file in each application before making changes in the other.
N OT E If you forget and make changes in the application, you can preserve those changes by saving the file to a new filename. At least then it can be a reference if you want to incorporate the changes into the original document. ■■
The CustomUI Editor does not have a Find/Replace utility, so if you’re planning to do large-scale editing on your XML, you may want to copy your information to another program, edit it, and then copy it back.
■■
When working on files that have more lines of XML than will fit on the screen, the CustomUI Editor has an annoying habit of refreshing the screen so that your cursor is always on the last line of the screen. If you are trying to make an edit and want to read the information three lines below, this can become a major irritation. Here, again, you may want to copy your XML data into another editor to work on it and then paste the updated copy back into the CustomUI Editor.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 43
Chapter 2
■
Accessing the UI Customization Layer
XML Notepad XML Notepad is another tool that you may find of interest when you are editing or writing your XML code. It is another free download from Microsoft itself. XML Notepad enables you to snap in an XML schema that will validate your code as you work. Unfortunately, as great as this is, it can be a little cumbersome to get your code into the interface. The following detailed steps walk you through linking this all together and getting your code in.
Installing XML Notepad First, download and install the XML Notepad file. At the time of this writing, it was available at the following URL: ■■
www.microsoft.com/downloads/details.aspx?familyid=72d6aa49-787d-4118 -ba5f-4f30fe913628&displaylang=en
Next, you’ll also want to download and extract the Office 2007 XML schema: ■■
www.microsoft.com/downloads/details.aspx?FamilyId=15805380-F2C0-4B80 -9AD1-2CB0C300AEF9&displaylang=en
N OT E The Office 2007 XML schema page makes reference to Outlook, OneNote, and Visio, but it fails to mention the more popular applications. Just ignore that little oversight and download the file anyway, because those applications are included as well. The final step in setting up the XML Notepad program is linking the schema to the program. Open the XML Notepad program, and choose View ➪ Schemas from the menu. When you reach the XML Schemas screen, choose File ➪ Add schemas and browse to the folder to which the XML schemas were extracted. (The default location is C:\2007 Office System Developer Resources\Office2007XMLSchema\CustomUI.xsd). Once the schema has been added, as shown in Figure 2-15, click OK.
Figure 2-15: Adding an XML schema to XML Notepad
Using XML Notepad The most difficult part about using XML Notepad is getting your file into it to begin with. It does not just open Excel or Word files like the CustomUI Editor; and, sadly, you
43
91118c02.qxd:WileyRedTight
44
Part I
■
11/28/07
9:14 PM
Page 44
The Building Blocks for a Successful Customization
can’t even create a new file from within it. Instead, you need to open an existing XML file. There are two main strategies for doing this: ■■
You can rename your document to a zip file, as you would do when editing with Notepad, and copy the CustomUI.xml file out so that you can work on it. Naturally, when you’ve finished editing your code, you’ll need to copy the updated CustomUI.xml file back into the zip container and again rename the .xlsx file to use the original file extension.
or ■■
You can create a blank text file, copy in your existing XML from the CustomUI Editor, save the file as an XML file, and then open it in XML Notepad. At this point, you could edit the code and then send it back to the CustomUI Editor, where you would save it with your file.
You’re probably now wondering why you would want to go through all this pain. The answer lies in the ability to use the XML Notepad program to your advantage. Unlike the CustomUI Editor, this program offers you several features to easily create valid files without any risk of typing errors. That, as any developer knows, is a huge benefit. To demonstrate this: 1. Open the MyFirstUIModification.xlsx file in the CustomUI Editor. 2. Copy all of the code there and then close the CustomUI Editor. 3. Right-click your desktop and create a new text file. When prompted for a name, call the file temp.xml. 4. Right-click the new file and choose Edit (do not choose Edit with XML Notepad at this stage). 5. Paste the code that you copied from the CustomUI Editor. 6. Save the file and close it. 7. Right-click the file again, but this time choose Edit with XML Notepad.
N OT E If you are working in Windows XP, the Edit with XML Notepad option will not be present on the right-click menu. You will need to choose Open With and select the XML Notepad program from there. Once you are in the program, click to expand all the little plus (+) icons, and you should be looking at a screen like the one shown in Figure 2-16. Talk about a graphical display. You just entered developer heaven.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 45
Chapter 2
■
Accessing the UI Customization Layer
Figure 2-16: XML Notepad
Right-click the “tab” element, choose Element, and then choose Child. As shown in Figure 2-17, this will create a new element under the last group.
Figure 2-17: Adding an element to XML Notepad
Notice that the program provides you with a list of all the items that will fit here — in this case, the “group” element. Click it to set this element as a group in the code. Next, right-click the group, choose Attribute, and then choose Child. This time the list will be much longer. You’re after idMso, so scroll until you find it, and then click to select it. Once you’ve done this, you’ll notice that your cursor appears on the right side of the screen. Click on the line corresponding to idMso, and type in the following casesensitive text: GroupStyles (see Figure 2-18).
45
91118c02.qxd:WileyRedTight
46
Part I
■
11/28/07
9:14 PM
Page 46
The Building Blocks for a Successful Customization
Figure 2-18: Adding an attribute to XML Notepad
Now that you have some newly modified code, you’ll want to try it out. From the View menu, choose Source, and select Yes when prompted to save your code. You’ll then see a screen like the one shown in Figure 2-19, which shows your new markup.
Figure 2-19: Viewing XML source code in XML Notepad
Notice that the code is in a slightly different format than what was previously written, and that there are some extra elements that you might not have seen before. That is to be expected, as the XML Notepad editor adds markup that is valid but not required. Don’t worry if the code that you create manually does not have all of these tags. Now that you’ve had a good look at the view shown in Figure 2-19, you’ll want to do the following: 1. Copy the entire set of code and close the window. 2. Open your MyFirstUIModification.xlsx file in the CustomUI Editor. 3. Press Ctrl+A to select all the existing code. 4. Paste the code that you copied from the XML Notepad program. 5. Run a validity check (just to be sure), and then save and close the file. Finally, open the MyFirstUIModification.xlsx file and check the appearance of the My Very Own Tab tab. It should look similar to what is shown in Figure 2-20.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 47
Chapter 2
■
Accessing the UI Customization Layer
Figure 2-20: The updated My Very Own Tab tab in Excel
If you chose to apply this customization in Word instead, it would look like what is shown in Figure 2-21.
Figure 2-21: The updated My Very Own Tab tab in Word
The Benefits of XML Notepad XML Notepad offers many benefits that are not available in the CustomUI Editor or standard Notepad, including the following: ■■
XML Notepad can make development easier because, with the relevant schema installed, valid elements and attributes are available from the drop-down lists, similar in function to the IntelliSense features available in most Microsoft coding tools.
■■
XML Notepad, unlike the CustomUI Editor, is capable of doing find-andreplace-style searches. This is a major benefit of the XML Notepad, and you will have ample opportunities to use it.
■■
XML Notepad includes a “Nudge” feature, which will move a block of code up or down as a unit. This feature does not exist in either Notepad or the CustomUI Editor.
■■
The stepped layout displayed in XML Notepad makes it easy to identify what elements are nested where, a feature that is not evident in Notepad or the CustomUI Editor.
■■
Because XML Notepad is linked to an XML schema, it can do live validity checking on-the-fly and report the errors that it finds, another feature that is not available in Notepad.
47
91118c02.qxd:WileyRedTight
48
Part I
■
11/28/07
9:14 PM
Page 48
The Building Blocks for a Successful Customization
The Drawbacks of XML Notepad Aside from the obvious problems of getting your file into and out of the editor, there are some other things that the CustomUI Editor offers that XML Notepad does not: ■■
The error-checking features of XML Notepad and the CustomUI Editor look for different things. Although selecting from the drop-down lists will build wellformed XML, XML Notepad does not actually validate your code. Therefore, these two programs should be used in tandem to give your code the best chance of being validated to perfection.
■■
The CustomUI Editor can generate callback signatures for you. You’ll learn in later chapters that you can look these up, but it is much easier to have them generated for you automatically. XML Notepad has no facility to do this.
■■
The CustomUI Editor was written to make building your custom user interface easy. In addition to automatic setup of certain code portions, it also provides an easy interface for attaching pictures to your files. XML Notepad lacks this capability.
■■
If you start your modification by creating your customUI.xml file in XML Notepad instead of the CustomUI Editor, you’ll need to manually link your .rels file, as shown earlier in this chapter. Therefore, we recommend that you use the CustomUI Editor to set up your initial linkage. However, the construction of the CustomUI code could still be done in XML Notepad and then copied to the CustomUI Editor for inclusion, as we did in the example.
A Final Word on Excel and Word Customizations This section has covered how to open Excel and Word files for customization, and it demonstrated a couple of tools that can be used to make the job easier. It’s important to realize that the CustomUI Editor and XML Notepad tools work in tandem with each other, and that they each have their own benefits and drawbacks. Despite the difficulty in porting code back and forth between the two programs, they offer complementary benefits, and neither program should be relied on to provide a solo solution for creating and editing XML files.
Microsoft Access Customizations Unlike Word and Excel files, which are based on the new OpenXML file standard, Microsoft Access is still a binary file. While the basic XML used for customization is virtually the same between all the applications, the binary file structure dictates that Access Ribbon customizations are loaded through a completely different method from that used with Open XML files, such as Excel or Word.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 49
Chapter 2
■
Accessing the UI Customization Layer
At first glance, customizing Access’s UI via a table may seem quite complex, but Access is actually rather flexible when it comes to customization, as you’ll see as you progress through this section.
Storing the CustomUI Information in Tables The most logical starting point when dealing with Ribbon modifications in Access is to hold the XML code in a table. It also happens to be the easiest route to a customized UI in Access. The following customization example demonstrates how to accomplish this.
Creating an Access UI Modification Using a Table This example builds the custom tab shown in Figure 2-22 in a new database project.
Figure 2-22: My Very Own Tab tab in Access
To get this customization up and running, you’ll want to do the following: 1. Open Access and create a new database. 2. Right-click Table1 on the left, and choose Design View. 3. When prompted, give the table the name USysRibbons. 4. Make sure that the table has the following three fields: ■■
ID (an autonumber field to serve as the index for the UI records)
■■
RibbonName (it should be a text type field)
■■
RibbonXml (it should be a memo type field)
5. Close the table, saving it when prompted.
N OT E If your table disappears in the All Tables view, simply right-click All Tables and choose Navigation Options. Check the box that says “Show system objects” and click OK.
T I P Naming the table with the “USys” prefix — for example, USysRibbons — ensures that the table will be hidden in the Navigation Pane unless the Show System Objects option is checked under the Navigation Options dialog.
49
91118c02.qxd:WileyRedTight
50
Part I
■
11/28/07
9:14 PM
Page 50
The Building Blocks for a Successful Customization
Next, open the USysRibbons table and add a new record to it. For the RibbonName field, type in a meaningful name (perhaps something such as MainRibbonUI). In the RibbonXml field, paste the following XML code:
TIP
The preceding code is available from the chapter download files, but if you are typing this by hand, you may want to do so in the CustomUI Editor discussed earlier in this chapter. While the CustomUI Editor cannot directly place the XML code in your Access database, it is a viable tool for checking your XML code for consistency. Keep in mind that this is sensitive to case, space, and punctuation.
Your table may resemble Figure 2-23 after you paste the XML code into the RibbonXml field:
Figure 2-23: Pasting XML code into the USysRibbons table
Next, save the table, and then close and reopen the database file. When it reopens, go to Access Options (in the Office Menu) and select the Current Database option. There, under Ribbon and Toolbar Options, you will find an option (Ribbon Name) to select your custom Ribbon UI from the drop-down list, as shown in Figure 2-24.
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 51
Chapter 2
■
Accessing the UI Customization Layer
Figure 2-24: Linking the RibbonUI to your database
Unfortunately, you’ll now be required to close the file one more time to allow the changes to take effect. Upon reopening the database, however, your UI should be loaded. Without question, this method is very time consuming. You have to go through many steps in order to have your UI recognized by the system before you can finally get the Ribbon to show. Even worse, it seems to force you to reopen the database more times than you have to do restarts after applying a service pack! You’d think that there would be a more efficient way, but apparently not yet. If you are testing many different UIs and need to swap between them, a better approach would be to use VBA to carry out this task for you. Obviously, you still need to spend some time writing your VBA code, but once it is done, you can reuse it as many times as you want. We will come back to such issues later in the book when you are introduced to some advanced concepts of Ribbon customization.
C R O S S-R E F E R E N C E See Chapter 16 for other deployment methods for your custom UI.
Access USysRibbons Caveat Despite the shuffle of opening and reopening the database to do the initial link up of the new UI, you’ll probably agree that this is a relatively minor hassle. Once you are through that initial pain, you’re off to the races and can customize to your heart’s content, right? Well, before you can sit back and relax, you should know that Access also suffers from a limitation in terms of using the USysRibbons table. For seasoned Access developers, it is a well-known fact that the maximum number of characters allowed in a memo field is 65,535. And while that seems like a lot, and it may be sufficient for most projects, you may have realized that a very complex UI
51
91118c02.qxd:WileyRedTight
52
Part I
■
11/28/07
9:14 PM
Page 52
The Building Blocks for a Successful Customization
could easily run into hundreds of thousands of characters. Therefore, it is quite conceivable that it could surpass the field’s character limit. What would happen if you tried to paste XML code that exceeded this limitation into the RibbonXml field? Well, it simply is not possible. Due to the character limitation, you would not be allowed to paste the UI into this field. Great, so now what can you do to get around this limitation? Luckily, when you are using DAO instead of the UI, the Memo field has a 1GB limit, which is also restricted to text and numbers (not binary data). So, although the 65K limit should be enough to contain most customizations, there’s actually a significant capacity that should be more than enough for even the most complex UI. But in the event that you need even more, we’ll mention some other techniques.
Other Techniques for Access UI Customizations By now you’re likely wondering what the heck to do if your customization becomes much bigger than you expected, and it blows past the 65,535 character limit. Fortunately, there are actually several ways to store your custom UI code, and only one has that limit. So far, some of the known methods you can use to store your UI XML are as follows: ■■
Store the XML in a table (as you’ve already seen).
■■
Read the XML from external files.
■■
Store the XML in another Access database.
■■
Link to Excel worksheets that contain XML.
In addition, if you run into the character limitation problem and are absolutely determined to store your XML code in the USysRibbons table (instead of having it in an external file), you can also turn to VBA for help. This does involve another layer of programming, but it is an available alternative. Because there are many more things that you need to know before becoming fluent in customizing the new UI, we’re going to hold off on discussing these additional methods. Rest assured, however, that there are indeed ways to get around the character limit. Chapter 16 illustrates some of these methods, providing examples of your UI customizations from external files and other Access databases.
Conclusion In this chapter, you have seen how to get your XML/RibbonX code for ribbon modification into the appropriate files. For Excel and Word files, you’ve seen the hard way to accomplish this task, as well as the easier ways to build and edit your code with a variety of free tools available on the internet. Unfortunately, while each of these tools is useful in its own way, each lacks the cohesion that would be nice in a single product. Some are better for setup and others are better for editing, but none come without drawbacks. It seems that
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 53
Chapter 2
■
Accessing the UI Customization Layer
playing in this field will involve some application switching until someone builds an all-in-one tool to work with Ribbon modification. For Access files, you’ve learned that there are several ways to implement the customizations, and you’ve walked through a simple example of using a table to do so. We’ve also alerted you to the limitations of storing customizations in a table. Finally, we’ve promised that later in this book, we will share some secrets about overcoming such limitations.
53
91118c02.qxd:WileyRedTight
11/28/07
9:14 PM
Page 54
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 55
CHAPTER
3 Understanding XML
This chapter explores the fundamentals of building a custom user interface in Office 2007. Chapter 2 discussed how to access the customization layer, so you are now ready to learn how to create the core XML framework needed to access and manipulate the built-in Ribbon. This chapter begins by exploring what XML is and how it factors into customizing the new UI. It then follows with an explanation of each of the core elements needed to create or modify the Ribbon. Each section builds on the ones before, and they each employ only XML code. If you’ve never programmed anything before, don’t worry. No programming knowledge is required to get started — at least, nothing more complicated than how to open the file for customization, which was covered in Chapter 2. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
What Is XML and Why Do You Need It? XML is an acronym for Extensible Markup Language, originally published by the W3C World Wide Web Consortium. It is not truly a programming language, as it lacks any mechanism to perform actions, but rather is a set of rules intended to simplify the sharing of data across platforms.
55
91118c03.qxd:WileyRedTight
56
Part I
■
11/28/07
9:14 PM
Page 56
The Building Blocks for a Successful Customization
As you saw in detail in Chapter 2, Office 2007 files are deployed in Microsoft’s OpenXML format, which is simply a zipped container holding several XML files. So why XML? The great selling point of XML is data structure. By leveraging XML technology, the Office 2007 programs are able to split like data into “chunks” and store them in the XML portions of the file. For example, you can envision a Word document with an XML table of “strings” (or words) in it. Assume that you have an entire story written that uses the word “magical” 100 times. Rather than store the seven-character string 100 times in the document, the XML structure may refer to string 102, saving four characters each time. This is a very simplified example, of course, but you can see that this kind of operation could quickly result in saving space. That is just one of the benefits of the XML structure. In addition, by having your data structured, it can be quickly indexed. This is important, as it enables other programs to search the index for specific strings (words) or other terms. A great example to demonstrate how indexing can add valuable functionality is to think of a banking process. Each time an e-mail is sent, the indexed XML underlying any attachments can quickly be checked for strings matching key fields or patterns (such as bank account numbers, social security/insurance numbers, and the like.) If these strings are found, confidentiality can be enforced by programmatically removing or encrypting the data. Both of the aforementioned benefits are great, but for the developer there is yet another factor that can prove its weight in gold. You can link XML schemas within certain programs to validate XML and thereby ensure that it will work as intended. The CustomUI Editor and XML Notepad, both discussed in Chapter 2, make use of XML schemas. Without these schemas, the CustomUI Editor would not be able to validate code, and XML Notepad would not be able to provide IntelliSense, which provides or prompts with the available object or functions when writing code. Again, this is all wonderful, and it’s nice to know what XML is all about, but why should we care? We care about XML because it is the heart of the Ribbon. In order to customize the Office 2007 user interface, you must write XML code; and while it is true that Visual Basic for Applications (VBA) can also play a huge part in customization, it is not always necessary. XML, on the other hand, almost always is. We say “almost always” because we will show you the proverbial exception that proves the rule by providing a customization that does not require XML, such as pop-up menus and some VBA customizations that appear on the Add-Ins tab. These are discussed in Chapters 15 and 16, respectively. The rest of this chapter focuses strictly on how to build your XML framework from the ground up. Beginning with creating a placeholder for your first button, this chapter will guide you through the XML required to build and modify the Ribbon. These concepts must be mastered before you can move on to fluently work with more complex customizations.
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 57
Chapter 3
■
Understanding XML
Essential Background Before you can start running with XML, you need to learn essential background information — namely, how XML is structured and how to write it. If you have used HTML in the past, certain things will be familiar to you. Otherwise, don’t to worry if you are starting from scratch; these concepts are straightforward and easy to learn.
Tags An XML document, such as the customUI.xml file you learned about in Chapter 2, works by placing verbal cues among the content, giving the document an immediately obvious structure. (This is true even if what is being structured is not that obvious.) The collection of all of these cues is referred to as markup (the M in XML). The verbal cues are known as tags, and they take a consistent format to be considered valid. Consider the following snippet of code:
Looking at the structure of the preceding code, note the use of the , and / signs. These characters have very special uses in XML: they denote where tags start and end. For example, notice that everything between the opening tag and the closing tag is included within the group as it appears on the Ribbon. The splitButton that is listed within the group uses a structure that is identical to the group. It starts with the tag and holds all the items following until the splitButton is closed by the tag. In the preceding example, only the Underline button would appear on the splitButton. Now look at the code for the buttons themselves. Notice that the / character sits at the end of the tag and that it doesn’t require a separate tag to close the element. This demonstrates two methods that can be used for opening and closing XML tags. You need to understand and use both methods to build the structure for your customUI. Both methods are listed in Table 3-1. Table 3-1: Forms for Opening and Closing Tags TYPE
FORM
Open and Close separate
Open and Close in one
57
91118c03.qxd:WileyRedTight
58
Part I
■
11/28/07
9:14 PM
Page 58
The Building Blocks for a Successful Customization
At this point, you should understand that each tag in XML contains at least one element and usually at least one attribute. If you examine the preceding code snippet, you’ll see that the first line uses a group element, which has an id attribute: . Whereas the elements and attributes are the portions within the angle brackets, the tag refers to the entire clause, from the < to the >. How, then, would you know which method to choose? Which do you use for a group and when? What about a button? These are all great questions that this chapter will clarify, leading to your understanding of the fundamental concepts being covered. The answers lie in the relationship between parent and child objects in the XML code. If you have much experience with programming, this concept will not be new to you. If you are new, however, you need to become familiar with it, so bear with us as we explain the coding “facts of life.” When working with code, every item that we deal with is referred to as an object. For example, objects include a group on the Ribbon, a button, a checkBox, or even a menu. Like humans, many (but not all) objects have children, and when a control has children, we refer to it as a parent control. One thing that should be made clear here is that, in the code world, a parent object usually (but not always) has child objects of a different type. A tab will have one or more group objects as children, and a group may have a combination of button, checkBox, and dynamicMenu child objects. The dynamicMenu object, however, is especially talented, and can have dynamicMenu children in addition to other children object types. Some parent objects even have an entire horde of children, referred to as a collection. As you would expect, each child can also be a parent to its own children. This is where the structure of XML can actually come in quite handy, as the relationships between parent and child can be easily seen. Every child control must be “nested” within its parent’s opening and closing tags. It should be noted that unlike humans, however, every child object may only have one parent. In the following snippet (from the code shown earlier), you can see that the splitButton object contains one child button and that the button does not have any children of its own. This is why the button ends with the / character, whereas the splitButton has separated opening and closing tags. Likewise, the group parent contains the splitButton child (with its own children), as well as two other button controls:
You should understand a few other things in order to create well-formed (valid) XML: ■■
All tags, be they elements or attributes, are case sensitive (a SPLITBUTTON element is not the same as a splitButton element).
■■
Attribute values must be enclosed in single or double quotation marks (it doesn’t matter which).
■■
The nesting of child elements within a parent is precise. Every start tag must be matched with its own end tag, whether it is closed within the same tag (via “/>”) or by a separate tag later.
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 59
Chapter 3
■
Understanding XML
These rules help highlight a quick visual flag, so to speak. If the / is by the closing > (as shown in the button line of the preceding code), you know that the tag is a complete, standalone tag. However, if the / directly follows the opening < ( characters. Each tag must include one, and only one, element. In addition, the element will always be in the first part of an XML tag. It tells the compiler which specific item you wish to start working with or stop working with.
Attributes Attributes are a very important aspect of XML as well. Unlike tags, which tell the compiler what object to work with, attributes tell the compiler about the object’s properties. Some examples include the object’s name, the caption that shows on the screen, and whether or not the object is visible. Unlike elements, you may set more than one attribute for an object within a given tag. The following code snippet shows an example of a tag with multiple attributes:
The preceding code gives a button a unique id and specifies a size, label, and image for the button. In addition, it provides an onAction callback signature that will launch a VBA procedure when the button is clicked.
C R O S S-R E F E R E N C E VBA callbacks are explained in great detail in Chapter 5.
The next two sections discuss two very important attributes that are used virtually everywhere: id and label.
59
91118c03.qxd:WileyRedTight
60
Part I
■
11/28/07
9:14 PM
Page 60
The Building Blocks for a Successful Customization
The id Attribute Before we create additional objects, we need to discuss how to identify them in code. The id attribute is used to identify a specific object within your custom XML code, giving it a name that you can use to refer to it later. As this is the only way to refer to your objects, some form of an id attribute is required for each object that resides within any of the following containers: ■■
contextualTabs
■■
officeMenu
■■
qat
■■
tabs
There are several different types of id attributes and each one has a different use. Table 3-2 describes the different types of id attributes and their main purposes. Table 3-2: Table of id Attributes ATTRIBUTE
WHEN TO USE
id
This is used to uniquely identify your control. If you’re loading items dynamically, these will be assigned for you.
idMso
This is used to uniquely identify a built-in control, tab, command, etc. Use this to interact with built-in objects.
idQ
This is used to refer to objects across a shared namespace. Shared namespaces are covered in detail in Chapter 16.
The following examples demonstrate the use of the id and the idMso attributes to refer to controls. You will learn how to construct the XML later, but for now we are focused on grasping concepts related to the id. In order to identify an object, you simply add the id attribute within the opening tag. For example, to refer to a tab by id, you’d provide the following XML:
N OT E The preceding snippet provides an example of how to include a comment by preceding it with the opening . XML comments are discussed later in this chapter.
Likewise, if we were trying to refer to the built in Font group, we’d point to it by using the following XML:
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 61
Chapter 3
■
Understanding XML
N OT E It is critically important that all id and idQ attributes be unique. Using an id that exists, or that is reserved by Microsoft, will result in an error and prevent your UI from loading. You can see where naming conventions can be your friend. This also raises a concern about the potential for conflicts when you start sharing customizations. In order to avoid conflicts with built-in Microsoft controls, it is advisable to prefix all of your custom controls using a standardized naming convention. A list of suggested prefixes, which we use throughout this book, can be found in Appendix E. Because each developer assigns the id of custom objects, you should employ common sense and good practices to minimize confusion or frustration.
The label Attribute Another important attribute is the label. The label is what the user can read onscreen. This does not need to be unique, but it should be logical, concise, and consistent. Whereas the id attribute is used to keep track of objects by the system, the label attribute is used to provide clear guidance to the user. As demonstrated earlier, adding a label merely requires adding a line of code to the XML. You simply follow the opening tag with as many attributes as you wish and then add the closing tag, as illustrated in the following snippet:
Tips for Laying Out XML Code If you have seen any XML for the new UI, you may have despaired at the coding mess that can come along with it. Who wouldn’t feel bewildered the first time they saw a block of text like the following?
61
91118c03.qxd:WileyRedTight
62
Part I
■
11/28/07
9:14 PM
Page 62
The Building Blocks for a Successful Customization
In fact, the preceding code is relatively short, and by now it should be reasonably easy to understand. After all, it has only a few clearly marked groups and buttons. This code creates a new tab, “My Custom Tab,” and inserts it before the default Home tab. The new tab has the group My First Group, which contains four buttons. You might have noticed that the first label is large, but the remaining three labels are normal. This XML also states that the buttons will invoke an action when clicked, but it does not actually include the code to implement the action. We’ll get to that in Chapters 4 and 5, when we discuss the basics of VBA that are associated with Ribbon customizations, and how to implement callbacks. As you read through the XML, you might feel as though it isn’t quite as clear as you first thought, and you might also realize that it is not enough to deploy a custom group on the Ribbon. This is where the trouble starts. It’s obvious that we need to add all sorts of attributes to tabs, groups, commands, and so on. Moreover, as you might expect, the code can grow rather rapidly and become nearly impossible to read. Obviously, trying to find a specific control in hundreds of lines of code similar to the preceding example can be aggravating. To that end, we suggest that you break up your code into logical blocks using hard returns and tabs. Fortunately, the XML code ignores these characters!
T I P Unlike VBA, you can include hard returns and tabs in the XML without affecting the code. Therefore, we highly recommend that you divide the code into easy-to-read-and-interpret blocks. Several good models are provided throughout this book and in the chapter downloads. Have a look at the following adjusted example. Clearly, it is much easier to read:
On the one hand, in the preceding reformatted example, the code is definitely longer. On the other hand, it is clear that the buttons belong to one specific group, as the command button is stepped inside its tag bracket. In addition, because each button’s attributes have also been indented, you can easily refer to them and edit them as necessary. Space really isn’t an issue, so it makes sense to use the whitespace to make it easy to read, interpret, and edit. Trust us, you’ll thank yourself when you are trying to work with something you wrote six months ago!
Creating Comments in XML Code It is inevitable that at some point you will want to place a comment in the XML code. Comments are excellent tools to give yourself or someone else a clue about what the code is doing. Comments can be invaluable in complex or unique situations. To include a comment, you start by opening it with a “less than” sign ().
If you think your comment may run to several lines, you might want to break it down between the opening and closing tags, as shown in the following example:
N OT E Because this is a comment, the block is opened with . You will also notice that there is no need to include special characters to create a carriage return or to precede each line. Everything between the is treated as one continuous comment. In a situation like this you do not have to worry about running into several lines of comments. You simply keep typing until you’ve said everything you need to, as the comment is not closed until you reach the --> tag. The comment will automatically adjust to the text wrapping provided by the editor.
63
91118c03.qxd:WileyRedTight
64
Part I
■
11/28/07
9:14 PM
Page 64
The Building Blocks for a Successful Customization
Another important point worth noting is that you cannot put a comment in the middle of an open block of XML code. To demonstrate this, we will look at three brief variations of inserting a comment for a button. The first example shows how to make a comment that precedes a button:
The preceding XML code is perfectly valid, and probably reflects the most common placement: with the comment preceding the button. It would be equally acceptable to insert the comment after the button was closed by the “/>” characters. The following variant is also acceptable, although it requires extra typing:
If you examine the preceding code, you will see that it is actually formed as . Because the initial button tag was closed by the > character, you can now insert child objects, including comments. Remember that this button tag still needs to be closed, though, hence the at the end. Conversely, the following XML variation is not acceptable and will generate an error because the note is inside an open set of tags: [attributes]/>. Notice how the button tag is still open? Each comment must exist between the tag that closes an element (>) and a tag that opens the next element (
Notice that the onLoad attribute is just nested within the same brackets that hold the customUI element. This would also be true of any of the other optional attributes as well. And, again, you list one attribute per line without any need to include code for continuation or line breaks.
Allowed Children Objects of the customUI Element The customUI tag is a container that may, and undoubtedly will, hold other objects. As explained earlier in the chapter, it is the parent object, so it may hold child objects. Actually, the customUI container may only hold either or both of the following elements/objects: ■■
commands
■■
ribbon
At this point, we’re ready to explore the ribbon element.
The ribbon Element Now that you know how to open the customUI object for editing, you are ready to move on to the next step. In order to start any modification to the Ribbon itself, you would nest the ribbon element within the customUI tag, as shown here:
At this point, you should insert the and tags around the XML comment and revalidate the code. Pay careful attention to how we are “encapsulating” tags according to hierarchy, generating the parent-child relationships that you learned about earlier. Each tag will be nested within the other, building a logical pyramid of modifications that is easy to read.
Required Attributes of the ribbon Element Unlike the customUI element, and indeed most of the other elements that you will see, the ribbon element does not have any required attributes and can be used entirely on its own.
67
91118c03.qxd:WileyRedTight
68
Part I
■
11/28/07
9:14 PM
Page 68
The Building Blocks for a Successful Customization
Optional Static Attributes While the ribbon element may not have any required attributes, it does have one very special attribute, shown in Table 3-5. Table 3-5: Optional Attribute of the ribbon Element STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
startFromScratch (none)
ALLOWED VALUES
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
true, false, 1, 0
false
(none)
The startFromScratch attribute is the attribute that enables you to hide the entire built-in Ribbon that Microsoft worked so hard to create. Notice that because it has a default value of false, we omitted it in the code snippet earlier. We could have achieved the same results with the following XML:
C R O S S-R E F E R E N C E This attribute is essential learning if you want to create your own UI entirely from the ground up, so it is covered in much more detail in Chapter 13.
Allowed Children Objects of the ribbon Element The ribbon object may only hold any (or all) of the following elements: ■■
contextualTabs
■■
officeMenu
■■
qat
■■
tabs
Graphical View of ribbon Attributes Using the code provided above, which included the startFromScratch attribute set to false, would have no effect on the display of the Ribbon, as no tabs, groups, or other modifications have been made.
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 69
Chapter 3
■
Understanding XML
Conversely, consider Figure 3-2, which shows how the user interface would have displayed had the startFromScratch attribute been set to true!
Figure 3-2: The Ribbon, with the startFromScratch attribute set to true
As you can see, the default Ribbon would be hidden, so only the Office Button and the drop-down for the QAT are displayed. Just because features aren’t visible, though, doesn’t mean that they aren’t available. In Chapter 13, we’ll build a Ribbon from scratch and discuss some of the features that remain, such as the shortcut keys.
The tabs Element Now that we have the customUI container and the ribbon container, the next major tag in the hierarchy is the tabs element. This is yet another container, which must nest within the Ribbon block. Modify your code to reflect the code that follows, and remember to validate your XML:
The purpose of the tabs tag is to collect the elements for each and every individual tab that you choose to reference, create, or modify.
N OT E Be aware that the preceding code, despite being well formed, will actually fail validation in the CustomUI Editor. That is because the XML code is expecting a tab child element. At this point, even if you open your file in the application, nothing would seem to happen.
Required Attributes of the tabs Element The tabs element is one of the easiest elements to use, as it does not have a single attribute of any kind, either required or optional!
69
91118c03.qxd:WileyRedTight
70
Part I
■
11/28/07
9:14 PM
Page 70
The Building Blocks for a Successful Customization
Allowed Children Objects of the tabs Element The tabs element is a container element that is used to hold specifically referenced (or created) tab controls.
The tab Element At long last, we are finally at the place where we can do something that will show up! The tab element resides in the tabs container, and is used to create or refer to an individual tab on the ribbon. It is extremely important to understand the difference between the tabs object and the tab object. While tabs (plural) refers to the entire collection of tabs, tab (singular) indicates a specific tab among many possible tabs (the collection of tabs). If you are familiar with VBA, you will recognize the use of collections. If you have customized any of the Office applications in Office 2003 or earlier, you are already familiar with this sort of hierarchy in the form of the application’s Commandbars object, which represents the collection of toolbars (command bars). By contrast, the Commandbar object refers to a specific object command bar object within the entire group.
Required Attributes of the tab Element The tab object is the first element in the hierarchy that requires an id attribute. One selection, and only one selection, must be made from the available options shown in Table 3-6, which also includes suggestions regarding when to use each type of id attribute. Table 3-6: id Attributes of the tab Element ATTRIBUTE
WHEN TO USE
id
When creating your own tab
idMso
When using an existing Microsoft tab
idQ
When creating a tab shared between namespaces
The reason why an id attribute is required is quite simple: If the tab didn’t have an id attribute, how would you know which tab you were referencing?
N OT E While you can employ multiple tab (or other) elements using a mixture of id and idQ attributes in a single customization file, it is unlikely that you would do so. For most purposes, you will simply create new tab elements using the id attribute and refer to existing tab elements using the idMso attribute. The idQ attribute is discussed in detail in Chapter 16.
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 71
Chapter 3
■
Understanding XML
Optional Static and Dynamic Attributes with Callback Signatures The tab element also provides developers with several optional static attributes. In order to set the position of a tab in relation to any other existing tab, you may want to use one of the insert attributes shown in Table 3-7. Table 3-7: Optional insert Attributes for the tab Control INSERT ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
WHEN TO USE
insertAfterMso
Valid Mso Tab
Insert after last tab
Insert after Microsoft tab
insertBeforeMso
Valid Mso Tab
Insert after last tab
Insert before Microsoft tab
insertAfterQ
Valid Tab idQ
Insert after last tab
Insert after shared namespace tab
insertBeforeQ
Valid Tab idQ
Insert after last tab
Insert before shared namespace tab
N OT E If you do not specify an insert attribute, then the tab is added immediately after the last tab, whether it’s a custom tab or a built-in tab. The tab control will also optionally accept any or all of the attributes shown in Table 3-8. Table 3-8: Optional Attributes and Callbacks of the tab Control VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
keytip
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 1024 characters
(none)
Sub GetLabel(control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 1024 characters
(none)
n/a
Visible
getVisible
true, false, 1, 0
true
Sub GetVisible(control As IRibbonControl, ByRef returnedVal)
71
91118c03.qxd:WileyRedTight
72
Part I
■
11/28/07
9:14 PM
Page 72
The Building Blocks for a Successful Customization
C R O S S-R E F E R E N C E Dynamic callbacks are used to dynamically modify the ribbon while the file is in use. They use Visual Basic for Applications (VBA) code to function. We explain how to use dynamic callbacks in Chapter 5.
Allowed Children Objects of the tab Element The tab object may only hold group elements such as the built-in Clipboard group or custom groups of your own making.
Graphical View of tab Attributes Figure 3-3 shows a custom tab on the Ribbon. The tab was inserted before the Home tab, and it has the custom keytip of S.
Figure 3-3: A new tab on the ribbon
Built-in Tabs When working with tabs on the Ribbon, there are two distinct kinds that you may play with: built-in and custom. The built-in tabs are tabs are provided by Microsoft, whereas the custom tabs are, of course, your own creation. We get into custom tabs later; for now we focus on built-in tabs.
Referring to Built-in Tabs Every built-in tab has its own unique idMso attribute, and you refer to the tab by calling that attribute. Indeed, the hardest part of referring to a tab is figuring out its idMso! Table 3-9 shows some of the more common tab names for Excel, Access, and Word. As with other code, the use of CamelCase is critical, as your code will fail if these appear completely in lowercase.
C R O S S-R E F E R E N C E Appendix B contains tables of all the built-in tab names for all three applications.
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 73
Chapter 3
■
Understanding XML
Table 3-9: Built-in Tab Names for the Most Common Tabs TAB NAME
idMso FOR EXCEL
idMso FOR WORD
idMso FOR ACCESS
Home
TabHome
TabHome
TabHomeAccess
Insert
TabInsert
TabInsert
(none)
PageLayout
TabPageLayoutExcel
TabPageLayoutWord
(none)
Formula
TabFormulas
(none)
(none)
Data
TabData
(none)
(none)
Review
TabReview
TabReviewWord
(none)
Create
(none)
(none)
TabCreate
External Data
(none)
(none)
TabExternalData
Database Tools (none)
(none)
TabDatabaseTools
Modifying a Built-in Tab Being able to reference built-in tabs gives us a little power to modify and leverage the built-in user interface. Our next example demonstrates this using Excel. Open Excel and create a new workbook. Because the example does not require any dynamic callbacks, you can save the file in Excel’s default, macro-free (xlsx) workbook format (as opposed to xlsm for files that contain macros). When you are done, close Excel and open the file in the CustomUI Editor. Apply the RibbonBase template from the Sample menu.
C R O S S-R E F E R E N C E The RibbonBase template was created in Chapter 2 and is used throughout this book.
N OT E Notice how the RibbonBase template includes the customUI, ribbon and tabs elements. This template was created because these items rarely change,
unlike the tab controls and their child objects. Therefore, applying the template gives you a consistent baseline and beginning point for all of the examples.
Between the and tags, insert the following XML code:
73
91118c03.qxd:WileyRedTight
74
Part I
■
11/28/07
9:14 PM
Page 74
The Building Blocks for a Successful Customization
Remember to validate and save the code. Close the file in the CustomUI Editor and then open it in Excel. The Home tab is gone! Don’t worry, though. Closing the file will make the Home tab reappear.
T I P The preceding XML code works equally well if you try using it in a Word document instead of an Excel file. To use it in Access, however, you need to change TabHome to TabHomeAccess. A quick review of the idMso names in Table 3-9 will show you why.
C R O S S-R E F E R E N C E To deploy the preceding code in Access, review the steps outlined in Chapter 2.
Custom Tabs While hiding built-in tabs can make for a great practical joke on an unsuspecting coworker, there is only so much use that you can get out of this capability. Fortunately, we have the toolset that enables us to create our own tabs.
Creating Custom Tabs To create a custom tab, we don’t reference the idMso, but instead for a new tab by specifying a unique id attribute. You’ll recall that idMso is reserved for tabs provided by Microsoft. Building on the previous exercise, close your example file in Excel, reopen it in the customUI Editor, and replace the code from ending. When you validate the code, you’ll see that it works just fine, but you may be wondering why we’ve done this. It’s very simple, actually. The previous examples were building custom groups with the expectation that controls would be nested within the group. In this case, we do not wish to make any modifications to the groups; we merely wish to place them on our tab. Therefore, there is no need for additional code, and the tag can be opened and closed on the same line. Once you are satisfied that your XML is “well formed” (validated), save and close the file in the CustomUI Editor. Open it in Excel and have a good look at the new tab that contains all of your favorite controls, as shown in Figure 3-8.
Figure 3-8: A custom tab populated with built-in groups
When taking this tab for a test drive, you’ll notice that the controls within the groups function exactly as they do in their native locations. You should also take note of the fact that the groups still reside in their default locations; a copy has merely been placed on the custom tab.
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 83
Chapter 3
■
Understanding XML
Custom Groups The painful reality of Ribbon customization is that despite the hard work that Microsoft has done, they just can’t anticipate exactly how we want our controls displayed. We may want only specific Microsoft controls arranged in our groups, we may want tabs of our own creation, and we may even want a mixture of the two.
Creating Custom Groups Like tab elements, custom groups are created by specifying a unique id attribute, rather than referencing the idMso of a built-in group. Close your example file in Excel, if it’s still open, and reopen the file in the CustomUI Editor. Immediately after the last group tag, add the following lines of XML code just before the tag:
Did you notice that this group does not open and close the group within the same line of code? Although this is a topic beyond the scope of this chapter, the reason is because you would only create a new group in order to fill it with other controls, such as the button, checkBox, or dynamicMenu. Separating the opening and closing tags establishes the framework for adding additional controls. Now validate the XML, save the file, and close it in the CustomUI Editor. Open it in Excel again. Notice that the My Group tab shows up as the empty group that it is, as expected, to the right of the other groups, as displayed in Figure 3-9.
Figure 3-9: A custom group on a custom tab
C R O S S-R E F E R E N C E The various types of controls that can be contained within a group container are discussed in detail in Chapters 6 through 11.
Positioning Custom Groups While the custom group did show up on our tab, perhaps it is not where we would like to see it. Suppose that you would rather have the custom group appear between the Clipboard and Font groups. It turns out that there are actually two ways to accomplish this.
83
91118c03.qxd:WileyRedTight
84
Part I
■
11/28/07
9:14 PM
Page 84
The Building Blocks for a Successful Customization
The first way to accomplish this would be to reference the idMso of the group that you want to appear either before or after. We demonstrated that earlier, so you’ll recognize the last insert line used here. This may cause the XML to look like the following:
T I P You could also swap the insertBeforeMso=”GroupFont” attribute with insertAfterMso=”GroupClipboard” to achieve exactly the same results.
While the preceding XML works, it also makes the code a little bit hard to follow as the groups are no longer created in the order in which they are displayed. This brings us to the second (and preferred) method for positioning a group. You can create the groups on the custom tab in the order you wish them to appear. Using that approach, the code would be as follows:
Try updating the code in the CustomUI Editor yourself. Whichever version you decide to use, it will display as shown in Figure 3-10 when you reopen the file in Excel.
Figure 3-10: The custom group is now displayed between the Clipboard and Font groups
91118c03.qxd:WileyRedTight
11/28/07
9:14 PM
Page 85
Chapter 3
■
Understanding XML
Custom Groups on Built-in Tabs While the preceding example placed a custom group on a custom tab, it is just as easy to insert your own group in one of Microsoft’s built-in tabs. We’ll demonstrate that here in Word. Create a new document in Word and save it in the macro-free docx format. Close Word, open the file in the CustomUI Editor, and apply the RibbonBase template. Between the and tags, enter the following XML code:
Once you’ve validated and saved the code, close the file in the CustomUI Editor. Upon reopening the file in Word, you will now see a custom group on the Home tab, as shown in Figure 3-11.
Figure 3-11: A custom group on Word’s Home tab
As you can see, we have added the custom group My Group to the built-in Home tab. In addition, we’ve positioned the group to be second, or at least to remain just to the left of the Font group. That’s all there is to it. You are now ready to create your own groups to hold the controls you wish to add.
N OT E If you try to create this example in Access using the steps outlined in Chapter 2, you would expect to be able to use the same code. Access’s font group on the Home tab is actually called GroupTextFormatting, not GroupFont as you’d expect. You can easily modify the code to work with Access simply by replacing “GroupFont” with “GroupTextFormatting”.
Conclusion This chapter began with a discussion of what XML is and why it is important to the Office 2007 user interface. You learned about tags, which are comprised of elements and attributes, and how they add structure to an XML document. The essential id and
85
91118c03.qxd:WileyRedTight
86
Part I
■
11/28/07
9:14 PM
Page 86
The Building Blocks for a Successful Customization
label attributes were explored, as well as tips on how to lay out your XML code to make it more readable. Because we can’t always rely on things being completely intuitive to the user (or our own memory for that matter), you also learned the syntax required to leave comments in your XML code. With the essential background out of the way, we turned our attention to the actual elements that are at the core of all Ribbon modifications. From the root customUI element to the ribbon element to the tabs element, we looked at how to nest the tags within one another to build the required customization hierarchy. Upon reaching this critical level of understanding, you should now be able to write some code that actually accomplishes visible results in the Office 2007 user interface. We started by creating and positioning new tabs. Of course, empty tabs aren’t of much use, so you learned how to place Microsoft’s built-in groups on your tabs. With that knowledge, you can quickly build a user interface that groups together the tools you use, and with minimal work on your part. Following the example of using the built-in groups, we also explored how to create custom groups. While the placement of controls in these groups is covered in Chapters 6 through 11, you have now learned how to create the fundamental building blocks to modify your UI. Where you proceed from here depends on whether you want to merely modify your Ribbon to organize the controls as you like, or you actually want to build custom controls into your files. If you are only interested in repositioning Microsoft’s built-in controls to make your Ribbon more efficient, you can learn about the individual controls in Chapters 6 through 11. Armed with the information from this chapter, you will be able to create your own tabs and groups, and fill them with built-in controls. If you are interested in creating your own controls, however, you need to learn about Visual Basic for Applications (VBA), which is covered in the next chapter.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Page 87
CHAPTER
4 Introducing Visual Basic for Applications (VBA)
In Chapter 3, you looked at some simple examples showing how to customize the Ribbon using XML. However, in order to create true custom solutions, you will need to add functionality. This is when Visual Basic for Applications (VBA) comes into play. This chapter has seven sections, each covering different aspects of VBA, such as recording macros, writing subprocedures and functions, debugging code, and error handling. Each section builds your knowledge and awareness of VBA. At this stage, the code is as simple as possible. The goal here isn’t to teach you all there is to know about VBA, but rather to provide a strong enough foundation for you to interpret, modify, and write code or code snippets to customize the Ribbon. Bear in mind, however, that this chapter is about learning the basics of VBA in order to add functionality to Ribbon customization; and because the visual effects are accomplished using XML code, which was introduced in Chapter 3, the examples in this chapter don’t even work with the Ribbon. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
87
91118c04.qxd:WileyRedTight
88
Part I
■
11/28/07
9:14 PM
Page 88
The Building Blocks for a Successful Customization
Getting Started with Visual Basic for Applications (VBA) In Chapter 3 you were introduced to XML code, which is used to add or remove tabs, groups, and controls from the Ribbon. However, just adding those is not enough. To add functionality to your customization, you need to work with VBA code. After all, what good is a new button on the Ribbon if it doesn’t do anything when clicked? Now it’s time to learn some of the fundamental principles and rules for programming with VBA.
N OT E When you work with built-in controls they will work as advertised — that is, all you need to do is to reference them and the customization will inherit all the behaviors and features of the built-in control, unless you state otherwise. Chapter 13 deals with overriding built-in controls and commands. If you are fluent with VBA, you may choose to skip this chapter, but you may benefit from breezing through the material to see some of the new ways that things work, such as how to get to the macro recorder. In addition, for those of you who have relatively little experience with VBA and/or macros, we highly recommend that you not only read through this chapter, but that you also spend some time practicing the exercises, as they lay the foundation for the rest of the book. Of course, you can always refer back to this chapter as you are working through the examples in later chapters. How VBA will apply to your work depends on the application you are working with. In Excel, you find a set of objects that are associated with Excel; similarly, there is a separate set of objects for Access and for Word. Therefore, although you may be comfortable with using VBA and macros in one application, you might need a refresher in the other applications. In this chapter, we will look at some important aspects of the object model (or OM) for these three applications in order to provide key insights into how you can interact with the program to add the functionality you want. Generally speaking, the set of instructions you give in a VBA procedure is called a macro in Excel and Word. However, Access also has objects called macros, which come with some predetermined instructions you can readily use in your project, instead of writing the process in a VBA function or subroutine. You should not confuse Excel and Word macros with Access macros. They are not the same entities. Moreover, you should not mistake macro (a procedure in Excel or Word) with the macro object in Excel. Another significant difference between the applications is that Excel and Word have a macro recorder, but Access does not. As you’ll soon discover, the macro recorder is a very useful tool. For one thing, it enables you to quickly expose certain properties and methods that otherwise would take a lengthy time to ascertain by inspecting the object’s model. However, Access 2007 does have a very nice Macro Designer that includes fields for users to provide the macro name, condition, action, arguments, comments, and more. In addition, starting with 2007, the Access wizards generate macros instead of VBA code. Although VBA is not the only programming language you can use to program the Ribbon — you could also use languages such as C# (C-Sharp), C++, VB.NET, and VB
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 89
Introducing Visual Basic for Applications (VBA)
(using COM) — we discuss only VBA in this book because not only is VBA powerful, but it is also available to anyone who owns a copy of Microsoft Office. Therefore, everything in this book is much more accessible to anyone venturing into programming the UI for the first time and whose programming experience is limited.
What Is VBA? We’ve been referring to VBA, so it’s time that we explain what we’re taking about. VBA is a coding language that enables people to do things that would otherwise be impossible using the built-in tools. It can also be used to improve upon and automate certain tasks. Broadly speaking, for Excel and Word, VBA can be divided in two categories of code: recordable code and nonrecordable code. These two categories don’t apply to Access, as it does not offer the macro recorder feature. In addition, although Access wizards generate macros, none of the wizards relate to Ribbon customizations. Therefore, the discussions and examples using Access rely mostly on VBA, but there are also a few examples that show how to use Access macro objects. In the first case, to create recordable code, simply turn on the macro recorder (detailed steps explaining how to do this are provided later). Any user can record and play back a macro; it requires no special training or skills. You don’t even have to be able to interpret the code to be able to use it. In the second case, which uses nonrecordable code, although you may not need a lot of specialized training, you will need some knowledge of VBA; you’ll also need some familiarity with the object models of the particular application that you will be working with. The Developer Tab on the Ribbon contains the developer’s tools that you’ll use to work with VBA and to inspect the XML schema. This tab is not displayed by default, so you may want to add it to your Ribbon now. We include instructions for displaying the Developer tab in Chapter 1, in the section “Showing the Developer Tab.” The following sections introduce some tools that are useful for writing and working with code. If you are not familiar with VBA, you should study the tools, as they play a critical role in adding functionality to the Ribbon and creating a custom UI.
Macro-Enabled Documents Before delving into VBA code, you need to be aware that Excel and Word have two new file formats. One of the file formats for each application does not allow you to save embedded macros with it. If you have saved your Excel workbook or Word document as plain documents instead of macro-enabled ones, any code in the files will be permanently removed when you confirm that the file should be saved. Fortunately, you are warned and given an option to change your mind, as shown in Figure 4-1.
WA R N I N G If code is commented — flagged so that it will not run — in a workbook, sheet, or document module, be aware that both Excel and Word will delete the code when the file is closed. In addition, it doesn’t matter whether the file is macro-enabled or not. In order to avoid this unpleasant surprise, you must ensure that a standard module or class module is present in your project (with or without code).
89
91118c04.qxd:WileyRedTight
90
Part I
■
11/28/07
9:14 PM
Page 90
The Building Blocks for a Successful Customization
Figure 4-1: Choosing the correct file format
Because most Ribbon customizations require code, the document must allow macros to run. Therefore, it is good to establish the practice of saving your workbooks and Word documents as macro-enabled. This is not a blanket requirement for Microsoft Access, as it handles many things differently, as you are already learning. We’ll go into more detail about the relevant security settings in Chapter 17.
Using the Visual Basic Editor (VBE) After you’ve ensured that your workbook or document is macro-enabled, it is time to get started with the Visual Basic Editor (or VBE). The VBE is where you will be writing VBA code. To access the VBE window, do one of the following: ■■
Click the Developer tab ➪ Visual Basic (for Excel/Word only).
■■
Press Alt+F11 (for Excel/Access/Word).
If you are using Access you have two additional avenues: ■■
Click Create ➪ Other ➪ Macro ➪ Module.
■■
If you are in Design View, under the Design tab select Tools ➪ View code.
Table 4-1 describes some of the elements of the VBE window. We refer to several of these throughout the book as we walk through the examples. Table 4-1: Important Elements of the VBE Window ELEMENT
WHEN TO USE
Code window
The code window is where you type your VBA code (or where the recorded code goes). Each major object has its own code window. Standard modules and class modules also have their own code window.
Code window close button
Use this button to close the code window of an opened object.
Code window maximize/ restore button
Use this button to maximize/restore the code window of an object.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 91
Introducing Visual Basic for Applications (VBA)
Table 4-1 (continued) ELEMENT
WHEN TO USE
Code window minimize button
Use this button to minimize the code window for the opened object.
Immediate window
Use the immediate window to debug code. You send results from your code to this window or you can type instructions directly in the window.
Menu bar
This is the VBE menu and it remains the same as previous versions not only in terms of content but also in terms of looks.
Project explorer
This is a container for your project’s objects.
Properties window
A window that shows the available properties for the object that has the current focus
Title bar
If you’re having a hard time figuring out to which object the code window refers, look at the title bar. This is where the name will be.
Toolbars
Toolbars contain useful commands to help you to get your coding job done more easily.
VBE close button
Use this button to close the VBE working environment.
VBE maximize/restore button
Use this button to maximize/restore the VBE working environment.
VBE minimize button
Use this button to minimize the VBE working environment.
To make it easy to find an element when you’re ready to use it, Figure 4-2 provides callouts to each VBE element listed in Table 4-1.
Recording Macros for Excel and Word The best way to get to know the object model in Excel or Word is to record a macro. When you record a macro, Excel or Word will keep track of almost anything that you do in the working environment and record these actions as VBA code. We say “almost anything” because not all actions you perform are macro-recordable. Therefore, if you need to do something that cannot be recorded by a macro, you will have to generate the code on your own, or at least with the guidance of this book. For example, you cannot record the steps to create a function. However, that’s one of the things that we’ll be covering later.
91
91118c04.qxd:WileyRedTight
92
Part I
■
11/28/07
9:14 PM
Page 92
The Building Blocks for a Successful Customization
Project explorer Menu bar Title bar Properties window Toolbars
Code window
Code window close button Code window maximize/ restore button VBE close button VBE maximize/restore button VBE minimize button Code window minimize button
Immediate window
Figure 4-2: Visual Basic Editor window explained
N OT E Although recording a macro is a great way to discover a lot about the object model, you’ll also find that the macro recorder actually records a lot of stuff that you don’t need or want in your code. Therefore, cleaning up recorded code is a must. To get started with recording a macro (in Excel or Word), just follow three easy steps: 1. From the Developer tab, select Code group ➪ Record Macro. 2. The Record Macro dialog box will open (they are slightly different for Excel and Word, but similar enough that this example works for both). 3. With the dialog box open, define the following options (because they are options, you can skip them if you like. Both Excel and Word provide values for nonoptional items). Figure 4-3 shows the Record Macro dialog box for Word and Excel, respectively.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 93
Introducing Visual Basic for Applications (VBA)
T I P You can also find a handy macro recording button on the application’s status bar at the left bottom corner. You can show/hide this button by rightclicking on the status bar and choosing Macro Recording from the pop-up list. ■■
Macro Name (Excel/Word): Use this text box to enter a meaningful name for the actions you are about to record. If you do not specify a name for your macro, Excel/Word will create a sequential name starting at Macro1, incrementing each macro by 1.
N OT E Macro names need to follow good naming conventions. The name cannot include spaces or other special characters. We discuss naming conventions later in this chapter. ■■
Button (Word only): Use this option to assign the recorded macro to a button.
■■
Shortcut keys (Excel) and Keyboard (Word): Use these options to specify a keyboard shortcut for your macro. If you click Keyboard in Word, then another window is opened so that you can insert the key combination you want. This even includes using uppercase or lowercase letters, but you shouldn’t use numbers, spaces, or special characters.
■■
Store macro in: This text box specifies where the macro being recorded should be stored. By default, Excel stores the macro in the workbook that called the recorder, whereas Word will store the macro in the Normal.dotm file. It is not a very good idea to store customization and code in startup files. For the purposes of all the examples discussed in the book, we always save both, customization and VBA code, in the example files.
■■
Description: Use this option to specify a description for your recording.
Figure 4-3: Record Macro dialog boxes for Word and Excel, respectively
After filling in the text boxes, click the OK button to start your recording session. When you have finished recording, simply click the Stop Recording button under the Developer tab ➪ code group (or on the status bar if you decided to use our tip).
93
91118c04.qxd:WileyRedTight
94
Part I
■
11/28/07
9:14 PM
Page 94
The Building Blocks for a Successful Customization
N OT E The macro recorder does not record how long you take to perform your actions, only what you do. Hence, don’t worry if it is taking you what seems like forever to get things done while recording. However, be aware that it will record almost everything, including errors, so it’s a good thing that you can clean these up later. After you’ve stopped the recorder, open the VBE and the module code window, and then open the Modules folder and select the new module. When you open the module, you will see the code that was created to automate your actions. You can add comments and modify the code as necessary. Otherwise, if it does exactly what you want, simply leave it as it is. One critical difference between Excel and Word that you need to keep in mind when recording a macro is that Excel allows you to use the mouse while recording your actions. Word, conversely, records only keyboard entries. This means that although you can use the mouse to select text in Excel and then perform some kind of action with the text and the process will be recorded, in Word you cannot use the mouse in the document window. Any action performed with the mouse is not recordable in Word.
N OT E Although you cannot use the mouse to perform actions on the document, you can use the mouse to select commands on the Ribbon and/or select elements in the UI.
A Recording Example This section walks through a simple example of recording a macro. Suppose you have certain calculations in Excel for which you always insert a specific comment. Instead of typing the comment each time, you can record the actions associated with inserting the comment and then simply play back the recording. Here are the steps for recording such a macro: 1. Go to Developer ➪ Code ➪ Record Macro. The Record Macro dialog box will open. 2. Type the macro name (we used InsertComment). 3. Define a shortcut for it. We use Ctrl+Shift+C (simply hold down the Ctrl and Shift keys when typing the letter C in the box and Excel will show the full shortcut). 4. Click OK to start recording. Now you need to insert the comment. Follow these steps: 1. Select the cell in which the comment should go. 2. Go to Review ➪ Comments ➪ New comment. 3. Type in your comment (we typed “This is the comment!”). 4. When you’re done, go to Developer ➪ Code ➪ Stop recording.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 95
Introducing Visual Basic for Applications (VBA)
T I P You might find it quicker to right-click and select Insert Comment from the pop-up menu list. That’s it! You’ve saved your macro and you’re done! In the code window for the standard module created, you should see something similar to what is shown in Figure 4-4.
Figure 4-4: Code window for a standard module containing the recorded actions performed
Editing the Recorded Macro The first thing to notice about this procedure is that it will always insert the comment on cell A1, or whichever cell you created the comment in during the recording process. This may not be exactly what you want, as it’s more likely that you want to insert the comment on whichever cell you select. Also note that if a comment already exists in cell A1, then you will get the error message shown in Figure 4-5, which will grind your code to a halt.
Figure 4-5: Error when trying to overwrite an existing comment
Pity. You were just getting started and you end up in a situation like this. Don’t panic, however, as this can be resolved in several ways. For now we describe one possible solution; later we explain how to resolve some error-handling issues.
95
91118c04.qxd:WileyRedTight
96
Part I
■
11/28/07
9:14 PM
Page 96
The Building Blocks for a Successful Customization
To make this macro more feasible for reuse, we can edit the code to accomplish the following: ■■
Remove the old comment before adding a new one.
■■
Ensure that the comment is always visible.
The amended VBA code is shown here, followed by a detailed explanation: Sub InsertComment() ‘ InsertComment Macro ‘ Keyboard Shortcut: Ctrl+Shift+C Range(“A1”).Comment.Delete Range(“A1”).AddComment Range(“A1”).Comment.Visible = True Range(“A1”).Comment.Text Text:= _ “Robert Martin:” & Chr(10) & “This is the comment!” End Sub
Because we already have a comment in cell A1, we can use the Delete method of the Comment object to clear it up before inserting a new comment. Note, however, that if there is no comment in the cell, you get an error — just as you previously got an error message when trying to insert a comment in a cell that already had one. But that’s really just the beginning of the modifications, because you also want to insert the comment in the active cell, rather than always in A1. To put the comment in the active cell, replace Range(“A1”)in the code with ActiveCell: ■■
Except for the second line, all the other lines refer to the Comment object. The Comment object does not have an Add method (such as the AddComment method, which is available in the Range object, as shown in the recording example), which is what allowed us to add a comment to the cell.
■■
You could add the comment directly on the second line by specifying its text in the method’s argument: Range(“A1”).AddComment (“Robert Martin:” & Chr(10) & “This is the comment!”)
For now, this is all we will do in terms of recording, but we’ll come back to this example later, when we develop error handling and again when we are working with block constructs of code.
Editing Macro Options After Recording Up until this point, we’ve assumed that you would record and change all of your macro options when you started. However, what if you wanted to change something later? This is not a major issue; in fact, it is relatively easy, as you’ll see by going through the following steps in Excel: 1. With your workbook open, click Developer ➪ Code ➪ Macros to open the Macro list dialog box. 2. Select the macro you want to change and then click Options to open the Options dialog. 3. Change the details and click OK to continue.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 97
Introducing Visual Basic for Applications (VBA)
As you might expect, the process is slightly different for Word. In the previous example, you could change the assigned shortcut keys directly in the Options dialog box. However, if you are working with Word, you need to go to a different location. To get you familiar with the process, we’ll use that location in the following example. With your Word document open, follow these steps: 1. Click Office Button ➪ Word Options ➪ Customize. 2. Under Keyboard shortcuts, click the Customize button. 3. Under Category, scroll to Macros and select it. 4. Under Macros, select the macro for which you want to reassign the shortcut keys. 5. Specify the key sequence. 6. Choose where you want to save the macro and click OK to finish. Figure 4-6 shows the Customize Keyboard window, where you change the shortcut key for a Word macro.
Figure 4-6: Editing macro options in Word
Subprocedures versus Functions Unlike the previous section, the subprocedures and functions described here apply to all three applications: Excel, Word, and Access. The differences are only relevant when dealing with specific object models that are unique to one application. However, the object models can be referenced and used across all three applications. (Referencing is covered later in this chapter.) We point out any instances of application-specific code in the examples — otherwise, the code should run without a problem in any of the three applications. We also provide more information about the individual objects as they are used in the examples.
97
91118c04.qxd:WileyRedTight
98
Part I
■
11/28/07
9:14 PM
Page 98
The Building Blocks for a Successful Customization
Object Model The object model (OM) enables you to access and control objects in the application. The Office OM has the Application object as the highest ranking object in the hierarchy of OMs. As such, it is commonly referred to as the root object. The OM is basically a set of objects and collections of objects that expose properties and methods that can be used to perform a variety of actions. Although some of these properties and methods are not directly accessible from an object, they can be reached by following the hierarchical path in the model. This is why it is important to understand both the concept and syntax for working with the OM. For example, suppose that you want to add a table to a Word document. Although it might be tempting to jump from the Application object straight into the Add method of the Tables collection, that would be skipping some steps. Instead, you must include the complete path from the root object to what you want to work with, as demonstrated in the following line of code: Application.Documents(“Document1”).Tables.Add
The number of objects varies depending on the application and object; and each object has its own properties and methods. In the preceding example, we start at the root object and move to the Documents collection, singling out “Document1” in the collection. We then use the Add method to add a table object to the Tables collection. Notice that collections are in the plural, whereas an object is in the singular. Similar to the way in which we move from the highest-ranking object to the lowestraking object along the path, we can also move from the lowest-ranking object to the highest-ranking object by using the Parent property of the low-ranked object. This is particularly helpful when you want to learn something about the parent object, such as the directory of a file, or the name of a parent object. Consider the following line: MsgBox ThisWorkbook.Parent
In the preceding example, the message box will show Microsoft Excel as the parent (or higher-ranked object) of the ThisWorkbook object in the OM hierarchy. If you wish to study the hierarchy of the object models for the applications discussed in this book, you can open the VBA Help and inspect the Object Model Map. In order to access this resource, open the Help window and then click the Home button ➪ Application Object Model Reference ➪ Application Object Model Map ➪ Object Model Map.
Subprocedures Now that you know a little more about the object model, we can move on to subprocedures. We’re going to use them, so it’s helpful for you understand a little bit about what they are. A subprocedure is basically a set of instructions designed to perform a certain task or set of tasks when it is executed. For example, you could create a procedure that opens a report in Access, or create a procedure that turns formulas into text in Excel.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 99
Introducing Visual Basic for Applications (VBA)
Take a moment to review the following procedures and then we’ll explain the parts and what they do. The following snippet demonstrates an Excel example: Sub WorkingWithCell() ThisWorkbook.Windows(1).ActiveCell.ClearContents ThisWorkbook.Windows(1).ActiveCell.Value = Now() ThisWorkbook.Windows(1).ActiveCell.NumberFormat = _ “dd-mm-yyyy hh:mm:ss” End Sub
Here is the Access example: Private Sub cmdExit_Click() CloseCurrentDatabase End Sub
Finally, the following illustrates a Word example: Sub TableFit() ThisDocument.Tables(1).AllowAutoFit = True End Sub
In the first example, we are working with a cell.
N OT E Because the term ActiveCell is used, this is not specifying a particular cell, but instead will work with whatever cell is currently the active cell in an Excel (the application) window.
In the second example, we’re dealing with a database in Access. In the final example, we’re dealing with a table in Word. In looking at the three examples, you can discern a few important objects: ■■
ThisWorkbook: This is native to Excel and refers to the workbook that contains the code. There are other ways to refer to a workbook, but if you need to work with workbook-level elements, use this object to do so (ThisWorkbook is also a property of the Application object, which returns a workbook object).
■■
ThisDocument: Similar to Excel, Word uses ThisDocument as the means to access relevant properties and methods for the document that contains the code.
■■
CurrentProject: This object is native to Access and is used to retrieve important information about the database project that contains the code, such as the connection string, the path, and so on.
For example, you could use ThisWorkbook in the following way to identify the author of a workbook: Sub ThisWB() MsgBox ThisWorkbook.BuiltinDocumentProperties(“Author”) End Sub
99
91118c04.qxd:WileyRedTight
100
Part I
■
11/28/07
9:14 PM
Page 100
The Building Blocks for a Successful Customization
Conversely, you could retrieve the entire connection details from your DB project with this code: Sub connectionDetails() MsgBox CurrentProject.Connection End Sub
If you haven’t seen the complete connection details before, you’ll be surprised by how much information is returned. Figure 4-7 shows an example of what you will see.
Figure 4-7: Message box showing connection details for an Access database
Functions Functions are normally used to return values, unlike subprocedures, which typically perform a task. For example, if you want to get the results of a calculation, then you would use a function. As an example, the following function calculates the nth root of a number: Function nthRoot(number As Double, nth As Integer) As Double nthRoot = number ^ (1 / nth) End Function
This function can be used as written in all three applications. These terms and the syntax perform equally well in Excel, Access, and Word. Functions can be used directly or indirectly. This means that you can use the function to calculate values in forms or in a worksheet; or you can call the function from another procedure within your VBA project. The call could come from another function whose final value depends on an intermediate calculation or it could come from a subprocedure. Using an Access form, you could implement the nthRoot function as shown in Figure 4-8.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 101
Introducing Visual Basic for Applications (VBA)
Figure 4-8: Implementing the userdefined function (UDF) in Access
The text box for the result contains the following formula =nthRoot([txtNumber], [txtnthRoot]) , where txtNumber is the text box containing the number and txtnthRoot
is the text box containing the Nth Root. This formula calls the function that returns the nth root of the chosen number. Of course, this is a simple example, and a function can be as complex as needed or imagined. Now that you’ve seen some basic aspects of subprocedures and functions, in the following section we turn our attention to some advanced aspects of VBA programming.
VBA Coding Techniques We’ve already discussed some basic aspects of VBA, such as macro recording, macroenabled documents and workbooks, and we’ve briefly covered subprocedures and functions. However, this only scratches the surface of VBA. In this section you are introduced to some coding techniques that are extremely useful when developing in VBA. You will find yourself in situations where you are unable to finish a task unless you use loops to repeat a process a certain number of times or until a specified result occurs. In other scenarios, you might need to structure your code so that it becomes more readable — in other words, using code blocks such as With-End With. In others, you will need to make decisions within the code, which is typically done using If-Then and Select Case statements (aka constructs). In addition to providing the desired functionality, code blocks also make it easier for you and other developers to interpret your code and make modifications when appropriate to accommodate changes in processes or business rules. All these scenarios are discussed here so that you can start implementing more advanced coding techniques in your projects.
Looping Statements Our first stop is to see some examples of looping constructs. Loops enable your code to run through a sequence and perform some sort of action — either a set number of times (as below) or until a condition changes (True/False).
101
91118c04.qxd:WileyRedTight
102
Part I
■
11/28/07
9:14 PM
Page 102
The Building Blocks for a Successful Customization
Loops come in a variety of flavors. We will look at each of them separately. If you’re not familiar with loops, you should study the examples, as loops are extremely important in some of the coding that we will develop later.
For-Next Loops This kind of loop can work as a counter or as a loop of elements within a predetermined set. We’ll provide a working example momentarily, but first let’s review the syntax and the definition of the elements. The syntax for the counter is as follows: For counter = start To end [Step step] [statements] [Exit For] [statements] Next [counter]
In the Next [counter] part, you can omit the [counter]. However, if you have many nested loops, you may want to explicitly specify which counter you refer to by the Next keyword, as we’ll explain shortly. Table 4-2 lists each part of the loop statement, along with an explanation of what it does and whether it is required or optional. Table 4-2: Elements of the For-Next Loop PART
DESCRIPTION
counter
Required after the For keyword. Not required after the Next keyword. You must specify a numeric variable to be used as the loop counter. You cannot specify a Boolean or an array as a counter element.
start
Required. This is the initial value of counter.
end
Required. This is the initial final value of counter.
step
Optional. This specifies by what amount the counter is changed each time through the loop. If not specified, step defaults to one. A step can be negative if you wish to reverse the start-end position to “step up” the loop.
statements
Optional. Specifies one or more statements between For and Next keywords, which are executed the specified number of times.
This kind of loop will run through the specified items within the specified range, as shown in the following example: Sub loopExample() Dim i As Integer
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4 Dim iCount
■
Page 103
Introducing Visual Basic for Applications (VBA)
As Integer
For i = 1 To 100 iCount = iCount + 1 Next i MsgBox iCount End Sub
In the preceding example, the loop runs from 1 to 100 and adds 1 to the counter variable each time the loop occurs until it is over. This example will work with any of the applications discussed in this book. Keep in mind that the terms “group” and “collection” are often used interchangeably when discussing these types of processes.
T I P You may want to step through the code by pressing F8, as it runs very fast. By stepping through the code using the F8 key, you can watch the loop doing its magic. In the same manner as the For-Next loop, you do not need to explicitly specify the element after the Next keyword. However, as before, if you have many nested loops, you may want to explicitly specify it. The syntax for the elements loop is as follows: For Each element In group [statements] [Exit For] [statements] Next [element]
Table 4-3 describes each element of the For Each-Next loop statement. Table 4-3: Elements of the For Each-Next Loop PART
DESCRIPTION
element
Required. Variable used to iterate through the elements of the collection or array. For collections, element can only be a Variant variable, a generic object variable, or any specific object variable (e.g., Worksheet, Document, Database). For arrays, element can only be a Variant variable.
group
Required. Name of an object collection or array (except an array of user-defined types). As noted, a group can also be referred to as a collection.
statements
Optional. One or more statements that are executed on each item in group.
103
91118c04.qxd:WileyRedTight
104
Part I
■
11/28/07
9:14 PM
Page 104
The Building Blocks for a Successful Customization
As shown in Table 4-2, you can also step the loop — that is, you can specify a value for the loop increment other than 1. You can obtain the exact effect as the preceding loop as follows: Sub loopExample() Dim i As Double Dim iCount As Integer For i = 0 To 1 Step 0.01 iCount = iCount + 1 Next i MsgBox iCount End Sub
What we did above is akin to dividing a one-dollar bill into 100 cents (or 0.01). Thus, between 0 and 1 we have one hundred units, which is the same as counting from 1 to 100 in the first loop. Notice, however, that we changed the data type from Integer to Double for the i variable. This is necessary because the step is not an integer. If you left i as an integer, you would get an infinite loop because you would never manage to move from 0 to the next step, as 0.01 would be considered 0. The next example, Sub listFileNames, is applicable to Excel (it can also be adapted for Access and Word). The code reads through each file in the folder where the workbook is located and lists the files in the active worksheet. For this example, we reference the Windows Script Hosting Model in order to be able to use objects such as FileSystemObject, Folder, and File. This is a critical reference, as the following code will fail without this reference: Sub listFileNames() Dim fsoObj Dim fsoFolder Dim fsoFile Dim lngRow Dim strPath
As As As As As
New FileSystemObject Folder File Long String
strPath = ThisWorkbook.Path Set fsoFolder = fsoObj.GetFolder(strPath) lngRow = 1 For Each fsoFile In fsoFolder.Files ActiveSheet.Cells(lngRow, 1) = fsoFile.Name lngRow = lngRow + 1 Next fsoFile Set fsoFile = Nothing Set fsoObj = Nothing End Sub
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 105
Introducing Visual Basic for Applications (VBA)
N OT E The workbook must be saved in order for the procedure to work as the file will not have a path until then.
C R O S S-R E F E R E N C E See “Referencing,” later in this chapter, for details on referencing libraries.
Do-While/Do-Until Loops These loops are useful when you want to loop through certain instructions until a condition is met and it equates to true. The syntax for these two types of loops is as follows: Do [{While | Until} condition] [statements] [Exit Do] [statements] Loop
In the preceding case, you specify the condition prior to entering the loop. You could also evaluate the condition after you enter the loop: Do [statements] [Exit Do] [statements] Loop [{While | Until} condition]
Note that either version of the code will execute at least once. As pointed out, these two types of loops are useful when you want the loop to occur until a certain criterion is met, as shown here: Sub DoUntilLoop() Dim lngRow As Long lngRow = 1 Do Until IsEmpty(ActiveSheet.Cells(lngRow, 1)) lngRow = lngRow + 1 Loop MsgBox ActiveSheet.Cells(lngRow, 1).Address End Sub
The preceding loop will occur until it reaches an empty cell in the active worksheet. Once that point is reached, the address of the first empty cell is shown in a message box. The next example applies to Access (it can also apply to Excel and Word if those applications are accessing a recordset and looping through it): Sub DoWhileLoop () Do While (Not (rst.EOF))
105
91118c04.qxd:WileyRedTight
106
Part I
■
11/28/07
9:14 PM
Page 106
The Building Blocks for a Successful Customization
rst.MoveNext Loop End Sub
The preceding loop will occur while it is not the end of file (EOF). At each stage, you move to the next record in the recordset. When the end is reached, the loop finishes. A variation of the Do While loop could be the following: While (Not (rst.EOF)) rst.MoveNext Wend
This loop does exactly the same thing as the previous example, but some people don’t like the syntax of the Wend keyword because the consolidated format does not list the elements separately and the condensed style isn’t as easy to interpret.
With . . . End With Statement This statement is extremely useful when you need to execute a series of statements in a single object or a user-defined type. The general syntax for this statement is as follows: With object [statements] End With
Table 4-4 describes each element of the With-End With statement. Table 4-4: Elements of the With Statement PART
DESCRIPTION
object
Required. The name of an object or user-defined type.
statements
Optional. One or more statements to be executed on the object.
The great advantage of using the With statement is that you can execute a series of statements with the same object (or property) without the need to repeat the object’s name over and over. You will remember our example for subprocedures where we were working with the ActiveCell property in Excel: ActiveCell.ClearContents ActiveCell.Value = Now() ActiveCell.NumberFormat = “dd-mm-yyyy hh:mm:ss”
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 107
Introducing Visual Basic for Applications (VBA)
You can see that the ActiveCell property is repeated at every single line, but you could avoid that repetition by using the With statement. For example, the preceding code rewritten to use With – End With would be as follows: With ActiveCell .ClearContents .Value = Now() .NumberFormat = “dd-mm-yyyy hh:mm:ss” End With
As you can see, you qualify the ActiveCell property and then assign the values for each property of the object or call a method to be executed (in the case of the ClearContents method). This not only saves you the time of requalifying the ActiveCell property at each line, it also structures your code so that it has a cleaner layout, which makes it easier to read and understand. You can easily imagine how much time this approach will save if you are going to repeat the process with several objects. You merely write the code once, copy and paste it numerous times, and then replace the name of the object — and modify other relevant commands and values.
If . . . Then . . . Else . . . End If Statement The If-Then-Else-End If statement is a decision statement that executes a block of instructions according to whether the specified condition is met or not. Which statement (either the Then statement or the Else statement) should be executed depends on whether the condition is met or not. The short syntax for this statement is as follows: If condition Then [statements] [Else elsestatements]
Although this syntax is perfectly acceptable from the point of view of evaluating the condition and executing the statement, a better choice is to break the statement into blocks, as shown in the following example: If condition Then [statements] [ElseIf condition-n Then [elseifstatements] [Else [elsestatements]] End If
Table 4-5 describes each element of the If...Then...Else statement. As you can see, only a few of the elements are required. The optional elements make this a very versatile and powerful tool that can be expanded to handle multiple conditions.
107
91118c04.qxd:WileyRedTight
108
Part I
■
11/28/07
9:14 PM
Page 108
The Building Blocks for a Successful Customization
Table 4-5: Elements of the If...Then...Else Statement PART
DESCRIPTION
condition
Required. Refers to the condition that needs to be evaluated during the decision process.
statements
Optional in block form. Required in a single-line form that has no Else clause. One or more statements separated by colons; executed if the condition is True.
condition-n
Optional. Same as condition.
elseifstatements Optional. One or more statements are executed if the associated condition-n is True. elsestatements
Optional. One or more statements are executed if no previous condition or condition-n expression is True.
The following example shows a function that compares a part-string against a full string: Function isLike(varValue As Variant, strLike As Variant) As Boolean If varValue Like strLike Then isLike = True Else isLike = False End If End Function
The preceding function can be used in any of the applications discussed in this book. How you call the function depends on its use. In Excel, you could call it from a formula in a cell. In Access, you could use it in a formula or call it using a subroutine: Sub compare() MsgBox isLike(“robson”, “rob*”) End Sub
This subroutine compares the part-string “rob*” (starting with “rob” and ending in anything) against the “robson” string. In the preceding case, the function returns True. It would also return True if the main string were “robert”, “robertson”, or any other string containing “rob” in the first three letters and ending in anything. The last example uses a direct message box, but the comparison could also evaluate a condition: Sub compare() If isLike(“robertson”, “mar*”) Then MsgBox “The comparison is true.”, vbInformation Else MsgBox “The comparison is false.”, vbInformation End If End Sub
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 109
Introducing Visual Basic for Applications (VBA)
Select Case Statement The Select Case statement is also used in decision making. When a case is evaluated as true, its statement is executed. In addition, at the end, you also have a general case (Case Else), which can be evaluated in the event that all other case expressions evaluate to false. This provides a lot of flexibility and can make it easier to read complex operations than it is by using a series of If...Then...Else statements. Additionally, with the Select Case statement, the conditional portion is taken care of within the evaluation of each case, instead of using several nested If...Then statements. The general syntax for the Select Case statement is provided here: Select Case testexpression [Case expressionlist-n [statements-n]] [Case Else [elsestatements]] End Select
Table 4-6 describes each element of the Select Case statement. Table 4-6: Elements of the Select Case Statement PART
DESCRIPTION
testexpression
Required. Any numeric expression or string expression.
expressionlist-n
Required if a Case appears. Delimited list of one or more of the following forms: expression, expression To expression, Is comparison operator expression. The To keyword specifies a range of values. If you use the To keyword, the smaller value must appear before To. Use the Is keyword with comparison operators (except Is and Like) to specify a range of values. When not supplied, the Is keyword is automatically inserted.
statements-n
Optional. One or more statements executed if the test expression matches any part of expressionlist-n.
elsestatements
Optional. One or more statements are executed if the test expression doesn’t match any of the Case clause(s).
Keep a few things in mind when using the Select Case statement: ■■
The case being evaluated must match exactly the expression text specified, and it is case sensitive. Thus, if an expression specifies “text” but the case is “Text”, then it will evaluate to false.
■■
If the expression can be matched to more than one case, then only the expression text belonging to the first case after the expression is evaluated.
■■
Use Case Else to evaluate an expression when there is no match in the cases specified.
109
91118c04.qxd:WileyRedTight
110
Part I
■
11/28/07
9:14 PM
Page 110
The Building Blocks for a Successful Customization
The following example helps illustrate what the second bullet above means: Sub caseExample() Dim Number As Integer Number = 1 Select Case Number Case 1 MsgBox 1 Case 1 MsgBox 2 Case 1 MsgBox 3 End Select End Sub
Because all cases are 1 and the number is 1, only the first case is evaluated, and only MsgBox 1 will be displayed. You typically won’t find code that matches this example, however, because there would be little benefit to having the second and third message boxes. Nonetheless, it effectively illustrates an important aspect of case statements — namely, that because the comparisons are made in sequential order, you only get the results specified by the first match. The next example demonstrates how the Case Else occurs when there is no match: Number = 2 Select Case Number Case 1 MsgBox 1 Case 1 MsgBox 2 Case Else MsgBox “There is no case for the specified number.” End Select
Here, the case number is 1; and since the number passed is 2, the Case Else is evaluated.
Writing Your Own Code You’ve now reviewed the basics of VBA and learned some important programming aspects. This section introduces some concepts that will be useful for writing your own code, such as naming conventions and data types. Also included in this section is a discussion of referencing libraries, which are extremely important when you need to bring external components into your own application.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 111
Introducing Visual Basic for Applications (VBA)
Naming Conventions This book is about a new technology, and as you’d expect, people are writing XML code and declaring names in various ways. While this may be convenient to them, it can be a problem if the code needs to be interpreted or used by others. The idea behind naming conventions is to provide an easy way for others to understand your code, and you as well for that matter. Trust us, it is not uncommon to be baffled by code in a project that you wrote years or even months before. For the purposes of standardization, we use RVBA (Reddick VBA, see note below) as the naming convention for the VBA code presented here. For the XML, we devised our own naming convention based on RVBA and the naming conventions used in VBA itself. We discuss them in this chapter in a moment. You can also refer to the appendix for some tables of the more common names. Do you have to use a naming convention? The answer is “no,” but we strongly recommend that you do. The benefits are worth the slight effort to learn the standards. Implementing naming conventions offers several benefits: ■■
It makes code easier for you and others to read and interpret.
■■
It reduces the chances of using a reserved word or special character.
■■
It avoids name conflicts between the objects that you create and those in code from other sources, such as third-party add-ins.
■■
It makes it easier for you to interpret code from others — whether you’re adapting it to your own project, helping someone else find a solution, or just learning what the code is intended to do.
Admittedly, if you’re writing the code only for yourself, then you can typically get away with using whatever names you are comfortable with – providing that you don’t use reserved words or special characters. Even then, however, you’ll probably notice that you’ve adopted your own convention. Moreover, as soon as you start incorporating code from other sources, you will see that following naming conventions can avoid conflicts and save a tremendous amount of time troubleshooting and debugging. When you are working with many people on a project or you need to share your code with others, following a standard naming convention is the way to go. Think of it as a language (like English itself); while it is OK to have slang in any language, not everyone understands such parlance (vernacular). Conversely, almost everyone can quickly and consistently interpret the meaning of standard English.
N OT E The Reddick convention can be found at www.xoc.net/standards/ rvbanc.asp.
That said, now it’s time to focus on the naming convention that we’ve adopted for the XML code that you write. It consists of the following parts: ■■
Prefix: We have adopted the rx prefix to differentiate code written in VBA from Ribbon customization from that written directly on your project for your project.
111
91118c04.qxd:WileyRedTight
112
Part I
■
11/28/07
9:14 PM
Page 112
The Building Blocks for a Successful Customization
■■
Tag: We have adopted the RVBA tagging system to tag Ribbon controls. The tag is very important because it tells the reader of your code what the control or object really is. For example, a label control in VBA would have the lbl tag. When we translate this into our Ribbon naming convention, it would become rxlbl. By doing so, we know by the VBA code that this label comes from the Ribbon and not from a label control within the VBA project.
■■
BaseName: This is the description of the control. For example, you could have a Ribbon button and define its prefix and tag as rxbtn; however, what does this button do? If it were a demo button you could name it rxbtnDemo, making it more meaningful.
■■
Event suffix: You already have a button, but what happens when a user clicks it? Well, an event will be triggered. In order to make life easier, we use the common VBA event suffixes to describe such actions. Therefore, if you have an onAction attribute attached to the Demo button, the procedure should be named rxbtnDemo_click.
■■
Shared event: In the previous example, we have a click for the Demo button. However, what if you wanted to share this event with other buttons? Or with other controls that have the onAction attribute? You would not be able to add the tag as we did before, so you’d use a generic tag to indicate that the onAction attribute is shared among many different controls. For example: rxshared_click. Now you know that this click is shared by many other controls that have an onAction attribute. However, the click event can perform different actions depending on the control that called it. You will learn how to make that happen in Chapter 5.
■■
Repurpose suffix: We already mentioned that we use event suffixes to match the events of VBA. However, when you use a built-in command, you may want to repurpose its action using the onAction attribute. Since onAction returns a click suffix, it would not be clear to a reader of the code that it was a built-in command being repurposed. In such cases, we use the rx prefix followed by the idMso attribute of the control as the base name, followed by an underscore and the word ”Repurpose” to make it clear that the built-in command is being repurposed. For example, rxFileSave_Repurpose means that the FileSave command has been repurposed to perform some other action.
The naming convention we adopted for the Ribbon certainly doesn’t have the force that an International Treaty on Naming Conventions might have, but it is very appropriate for the scope of this book, and it is an excellent way to jump-start a naming convention for the Ribbon XML code.
Data Types As you start to develop code, you will want to be aware of data types. Knowing what a data type can contain and how it can function will prevent you from looking for a solution to a problem that could have been easily avoided in the first place.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 113
Introducing Visual Basic for Applications (VBA)
If you recall from the loop section, when we created the stepped loop, we had to change the data type from Integer to Double. If you have not run through the example yet, this is a good time to do so. Keep the data type as Integer and try to run the stepped loop. What happens? You’re probably stuck in a never-ending loop. Why? Because you have the wrong data type and the loop is unable to match the step. That’s a perfect example of our point that understanding data types can help you avoid problems.
N OT E If you are stuck in a loop and can’t get out of it, press CTRL+BREAK to break the code and end it. Table 4-7 lists the data types VBA supports. The table includes storage sizes and value ranges. Table 4-7: Some Fundamental Data Types DATA TYPE
STORAGE SIZE
VALUE RANGE
Byte
1 byte
0 to 255
Boolean
2 bytes
True or False
Integer
2 bytes
-32,768 to 32,767
Long (long integer)
4 bytes
-2,147,483,648 to 2,147,483,647
Single (singleprecision floating-point)
4 bytes
-3.402823E38 to -1.401298E-45 for negative values; 1.401298E-45 to 3.402823E38 for positive values
Double (double-precision floating-point)
8 bytes
-1.79769313486231E308 to -4.94065645841247E-324 for negative values; 4.94065645841247E-324 to 1.79769313486232E308 for positive values
Currency (scaled integer)
8 bytes
-922,337,203,685,477.5808 to 922,337,203,685,477.5807
Decimal
14 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; smallest non-zero number is +/-0.0000000000000000000000000001
Date
8 bytes
January 1, 100 to December 31, 9999
Object
4 bytes
Any Object reference
String (variable-length)
10 bytes + string length
0 to approximately 2 billion
String (fixed-length)
Length of string
1 to approximately 65,400 Continued
113
91118c04.qxd:WileyRedTight
114
Part I
■
11/28/07
9:14 PM
Page 114
The Building Blocks for a Successful Customization
Table 4-7 (continued) DATA TYPE
STORAGE SIZE
VALUE RANGE
Variant (with numbers)
16 bytes
Any numeric value up to the range of a Double
Variant (with characters)
22 bytes + string length
Same range as for variable-length String
User-defined (using Type)
Number required by elements
The range of each element is the same as the range of its data type.
Working with Events You had a taste of events when we provided a sample of code for the old customization. Now it’s time to learn more about events. As with loops, events also come in a variety of flavors. You can use built-in events or you can write custom events to handle specific tasks. In this section, you will learn how to harness the power of events and how you can use events to interact with other programs and files. Excel, Access, and Word can monitor many different types of events. Events are normally associated with objects that you commonly use. This section covers the following events: ■■
Workbook events: As the name suggests, these events are associated with Excel workbooks. There are many workbook events, such as Open, BeforeClose, SheetActivate, and so on. Workbook-level events must be stored in the workbook in which events are being monitored. Application-level events are discussed later in this section.
■■
Worksheet events: Again, this is native to Excel and is associated with events happening in a worksheet. For example, you could have a SelectionChange, Change, BeforeRightClick, and so on.
■■
Form events: These are events occurring in Microsoft Access forms, and include events such as Load, Open, MouseWheel, and so on. (Although there are events associated with other types of forms, for our purposes we’re limiting this to forms in Access).
■■
Report events: These are events occurring in Microsoft Access reports. Among these events you will find Open, Close, and so on.
■■
Document events: Document events are associated with Word documents. Here you will find events such as New, Open, and Close.
■■
Application-level events: Application-level events monitor events that you find in a document or workbook, for example, but which are not locked in the document or workbook container. There may be cases you need to monitor when a new workbook or document is created, when a file is sent to print, and so on. Here, you will need to add custom classes to handle such applicationwide events.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 115
Introducing Visual Basic for Applications (VBA)
Any event handler can be directly typed into the code window of the object you’re dealing with. However, this method is extremely prone to typing errors and entering the procedure incorrectly. If you intend to work with an event, the best way is to simply let VBE write the procedure stub. Momentarily, we’ll show you how to make that happen.
Workbook Events Figure 4-9 shows how to initiate the process.
Figure 4-9: Choosing workbook events in the workbook’s code window
The image is for Excel, but the process is the same whether you’re using Excel, Access, or Word. In this particular example, you are working with the code window for a workbook. The event is the Workbook_SheetBeforeRightClick. The first two steps to get VBE to add the procedure are as follows: 1. Select the object you want to work with from the object list (the left drop-down menu in the code window). 2. Select the event from the object’s event list (the right drop-down menu in the code window). This produces the procedure you use to handle the event. Our example generated the following procedure: Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, _ ByVal Target As Range, Cancel As Boolean) ‘ Your event handler code goes here End Sub
Note that this event controls the right-click event of all sheets in the workbook. Later in this chapter, when you learn about worksheet events, you will also learn how to control the right-click event on a particular worksheet. In looking at the following code snippet, you will notice that the Open event does not have any arguments. It is important to recognize that although most events have arguments, some do not. The Open event is an example of an event that does not have any arguments: Private Sub Workbook_Open() ‘ Your event handler code goes here End Sub
115
91118c04.qxd:WileyRedTight
116
Part I
■
11/28/07
9:14 PM
Page 116
The Building Blocks for a Successful Customization
Now let’s take a look at the arguments for our first example, the SheetBeforeRightClick event. As written, the sub has the following arguments: ■■
Sh: Refers to the sheet where the right-click occurred. The sheet is passed as an
object because it can be either a chart sheet or a worksheet. ■■
Target: Refers to the target of the right-click and represents a range. The range can be a single cell or multiple cells.
■■
Cancel: Determines whether the return on the right-click should be canceled or not. You would normally get a pop-up menu when right-clicking. By setting Cancel equal to true, the pop-up menu is canceled.
To provide code to block the use of the right-click in the range A1:C25, you could use the following: Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, _ ByVal Target As Range, Cancel As Boolean) If Union(Target.Range(“A1”), Range(“A1:C25”)).Address = _ Range(“A1:C25”).Address Then Cancel = True End Sub
N OT E The preceding code assumes you’re dealing with a worksheet. If the object is a chartsheet, then the code will not behave as expected because a chartsheet does not have a selectable range as defined in the code. The preceding procedure will cancel the right-click on any cell within the specified range. You could, of course, choose any other range: If Union(Target.Range(“A1”), Range(“C10:E20”)).Address = _ Range(“C10:E20”).Address Then Cancel = True
Keep in mind that the preceding code affects every single sheet in your workbook. If you need to cancel the right-click on a specific sheet, you can use the preceding code in the worksheet module, instead of in the workbook module. We discuss worksheet modules next. Table 4-8 lists some of the workbook-level events. In order to view the list of available events, follow the process depicted in Figure 4-9. Table 4-8: Some Useful Workbook-Level Events EVENT
WHEN IT OCCURS
BeforeClose
The workbook is about to be closed
BeforeSave
The workbook is about to be saved
NewSheet
A sheet is added to the workbook
Open
The workbook is opened
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 117
Introducing Visual Basic for Applications (VBA)
Table 4-8 (continued) EVENT
WHEN IT OCCURS
SheetActivate
A sheet in the workbook is activated
SheetBeforeRightClick
The right-click of the mouse button is actioned
SheetChange
A change occurs on any sheet in the workbook
Worksheet Events The previous events are for the workbook, but they were also able to control what happened to sheets. When you want to work with events happening in one specific worksheet, you could not use the previous example, as it affects all sheets simultaneously. When it is necessary to work events on just one worksheet, a better alternative is to use the events for the worksheet itself. Take the example for the right-click. You could apply the same logic to just Sheet1, as shown in the following example, instead of to every worksheet in the workbook, as we did in the previous example: Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, _ Cancel As Boolean) If Union(Target.Range(“A1”), Range(“A1:C25”)).Address = _ Range(“A1:C25”).Address Then Cancel = True End Sub
You might immediately notice a few differences between the two examples: ■■
As a worksheet-level event, the argument for the sheet no longer appears in the procedure.
■■
The procedure is prefixed by Worksheet instead of Workbook.
Now let’s look at a scenario in which a worksheet event can be remarkably helpful. Suppose that you have validated column A to accept only unique values. Everything will work just fine, as long as the user inputs data into each cell and does not copy and paste within the range. That is a critical restriction, because if the user does copy and paste within the range, your validation will be wiped out. It’s times like these that event procedures really demonstrate their value. You can use the Change event to check what is happening in the column, and if the user pastes something, the procedure can reverse the pasting so that no duplicates occur in the column. The following code will accomplish your goal: Private Dim Dim Dim ‘
Sub Worksheet_Change(ByVal Target As Range) rngSelected As Range rngValue As Range lngCount As Long
Disable further events while we check what is happening Application.EnableEvents = False
117
91118c04.qxd:WileyRedTight
118
Part I ‘
■
11/28/07
9:14 PM
Page 118
The Building Blocks for a Successful Customization
The range being monitored If Union(Target.Range(“A1”), Range(“A1:A1048576”)).Address = _ Range(“A1:A1048576”).Address Then On Error Resume Next
‘
Sets the selected range Set rngSelected = Selection
‘
count the occurrence of the pasted value in the upper most For Each rngValue In rngSelected lngCount = WorksheetFunction.CountIf(Range(“A1:A1048576”), _ rngValue)
‘
If the occurrence is greater than one If lngCount > 1 Then
‘
Show the error message MsgBox “Column A only accepts unique values. “ _ & “The value/s you typed has/have already been” _ &”entered. Try again.”, vbCritical, _ “Value duplicated in column A...”
‘
Undo the pasting action Application.Undo Application.CutCopyMode = xlCut Exit For End If Next rngValue End If
‘
Enable events so that monitoring can continue Application.EnableEvents = True End Sub
Note that because the code also causes a change in the worksheet, you need to temporarily disable events. If you do not, the process may end up in an infinite loop. (That is accomplished in the line Application.EnableEvents = False.) In addition, of course, it is critical to enable events before exiting the Sub. In the preceding example, the first and last step in the routine take care of this.
N OT E It is standard practice to include the enabling of features in error handling routines. You’ll learn more about that later in this chapter when we discuss error handling.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 119
Introducing Visual Basic for Applications (VBA)
Table 4-9 lists some common worksheet-level events and when they occur. Table 4-9: Some Useful Worksheet-Level Events EVENT
WHEN IT OCCURS
Activate
The worksheet is activated
BeforeDoubleClick
The left button of your mouse is pressed/clicked quickly twice
BeforeRightClick
The right button of your mouse is pressed/clicked
Change
Something in your worksheet changes
SelectionChange
A selection in your worksheet changes
Form and Report Events in Access Now that we’ve covered some of the basics with Excel, let’s look at some events in Access. Here we’ll focus on the events for forms and reports, as these comprise the primary user interface. Envision a scenario in which a user opens a report, but there is nothing to see — that is, there is no data to be placed in the report. This suggests several undesirable possibilities. If there is no error trapping, then the user will get an error message. And even if you didn’t get an error message, what good is a report without data? A blank report can give the impression that there was an error in processing, rather than that there was no data to report. You can avoid many of these issues and potentially confusing outcomes by using the NoData event to determine whether the report should be canceled. In addition to canceling the report print event, the following code provides a nice explanation to the user: Private Sub Report_NoData(Cancel As Integer) MsgBox “No data available at present. The report will be“ _ &”canceled...” Cancel = -1 ‘True would also work here as -1 equates to true. End Sub
By setting the Cancel value to -1, you effectively set its value to True (meaning the report should be canceled). You could also use True as the value instead of the integer. Another useful event is the Open event. You can use this event to ensure that important elements are loaded before the report is shown. Quite often, users will provide information on a form that is then used to filter the data (the recordsource) for the report — for example, choosing an account and dates to filter the report. The report could open the form, be called from the form, or check to see whether the form is open, as shown in the following code snippet: Private Sub Report_Open(Cancel As Integer) DoCmd.OpenForm “frmInputData”, , , , , acDialog, “Transactions”
119
91118c04.qxd:WileyRedTight
120
Part I
■
11/28/07
9:14 PM
Page 120
The Building Blocks for a Successful Customization
If Not IsLoaded(“frmInputData”) Then Cancel = True End If End Sub
This example checks whether a particular form (frmInputData) is loaded before the report can be shown. If it is not loaded, then the report is canceled. A report also contains other elements that have events. For example, the Detail part of a report has events such as Format, which can be used to do such things as add lines or color to alternating rows as a report is about to print. By now, you should be able to interpret the following code, so give it a whirl and then read the explanation that follows: ‘ Module private count variable Private mRowCount As Long Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer) ‘ Counts the number of rows in the details at each pass mRowCount = mRowCount + 1 ‘ ‘
Determines the remainder of a division by 2. If zero, then BackColor 16777215 is applied (white background) If mRowCount Mod 2 = 0 Then Me.Detail.BackColor = 16777215
‘
Else, BackColor 15263976 (grey) is applied Else Me.Detail.BackColor = 15263976 End If End Sub
In this example, the formatting event fires for each line in the Details section of the report. When the line number is an even number, no color is applied, but if it is odd, a grey background is applied.
N OT E Although it required special code to add this type of formatting in prior versions of Access, 2007 provides some impressive formatting options that do not require code. In the report, go to Layout View and you can easily change the appearance of the report by selecting Report Layout Tools ➪ Format ➪ AutoFormat. Like reports, forms also share some of the same events, plus they allow you to add other objects that will have events of their own, such as combobox, textbox, and so on. If you have a data entry form, you can use the Open event to open the form to a new record, rather than display existing data. The following code snippet is just one of the ways to do that: Private Sub Form_Open(Cancel As Integer) DoCmd.RunCommand acCmdRecordsGoToNew End Sub
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 121
Introducing Visual Basic for Applications (VBA)
Just as a report has a Details section, so does a form. You will recall that when you right-click on a form, you get the contextual pop-up menu. We showed you how to cancel such pop-ups in Excel, and the same technique will work in Access. In fact, you may prefer to swap out the pop-up menu and use your own, as shown in Figure 4-10.
Figure 4-10: Pop-up menu triggered by a right-click event
The following code enables you to display your custom pop-up menu, whether it is replacing the standard pop-up or adding a pop-up where Access does not provide one: Private Sub Detail_MouseDown(Button As Integer, _ Shift As Integer, X As Single, Y As Single) If Button = acRightButton Then DoCmd.CancelEvent CommandBars(gcCMDBARNAME).ShowPopup End If End Sub
You first need to determine whether the click came from the right-hand button. If it did, then you cancel the event and show your own pop-up menu. In looking at the previous code sample, you can determine that the pop-up is globally defined as gcCMDBARNAME.
C R O S S-R E F E R E N C E See the section “Replacing Built-in Pop-up Menus in Their Entirety” in Chapter 15 for complete examples showing how to swap built-in pop-up menus with your own.
Table 4-10 lists some common report/form events and when they occur. The order of events can be very important, considering that the outcome of one event may prevent subsequent events from firing.
121
91118c04.qxd:WileyRedTight
122
Part I
■
11/28/07
9:14 PM
Page 122
The Building Blocks for a Successful Customization
Table 4-10: Some Useful Report and Form-Level Events* EVENT
WHEN IT OCCURS
Click
A mouse is clicked over the report/form
Close
A report/form is closed and removed from the screen
Current
The focus moves to a record (in a form or report), making it the current record, or the form is refreshed or requeried
Load
Occurs when a form/report is opened and its records are displayed
NoData
Access formats a report for printing that has no data, but before the report is printed
OnFormat
Access is determining which data belongs in a report section, but before Access formats the section for previewing or printing
Open
A form is opened, but before the first record is displayed. For a report, it occurs before a report is previewed or printed.
*Includes events for the Detail part of a report/form
Document-Level Events in Word Document-level events, as the name suggests, are events happening within a particular document in Word. For example, an event can be printing or it can be creating a new document. In Word, as in Excel and Access, you will find some common events, such as Open and Close, which you can use to perform some sort of action: Private Sub Document_Open() ‘ Your event handler code goes here End Sub
Although Excel provides other events straight from the code window for the object you want (such as the Document equivalent ThisWorkbook), Word does not do that. Instead, you need to create a class module to handle events for a particular document. You can also apply this method to global templates or to add-ins that control applicationlevel events in Word. The key to making this work is to use the WithEvents keyword to declare a public Word object that can be used across the project, as shown in Figure 4-11.
Figure 4-11: Choosing an event in a class window in Word
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 123
Introducing Visual Basic for Applications (VBA)
The first thing you need to do is add a class module to your project. In order to add a class module, with the VBE opened, click Insert ➪ Class Module (or press the Alt ➪ I ➪ C sequence). For this example, name it clsEvents. Now that you have named the module, open its code window (if it is not opened yet) and enter the following lines of code: Public WithEvents appWrd As Word.Application Private Sub appWrd_DocumentChange() End Sub
The first line of code says that the events for Word should be monitored. The procedure under it says that a document change should be monitored. As it is, nothing will happen. However, you can use the Open event at the document level to set the application-level events for Word: Dim mappWrd As New clsEvents Private Sub Document_Open() Set mappWrd.appWrd = Application End Sub
Once you have done that, you’re ready to use the event that you just declared in the class module, clsEvents. Remember, though, that you must write all your events first; otherwise, the class will be terminated because it has no events to monitor. Having said that, we’ll add an event that might be useful, as it will enable you to keep track of the printing of a certain document. We do that with the following code: Private Sub appWrd_DocumentBeforePrint(ByVal Doc As Document, _ Cancel As Boolean) If Doc = ThisDocument Then ’Log code goes here Else: ’Code for when printing another document End If End Sub
Notice that the WithEvents appWrd will monitor the entire Word application, so when you intend to monitor just the document that contains the code, you should construct the code to only execute if it is the correct document. In our example, we used the ThisDocument object to stipulate the document containing the code.
Application-Level Events Application-level events can be used to control what happens in different parts of the application. Because we just addressed this for Word, we’ll move on to Excel and Access. Before diving into this, however, it is important to note the differences in how each program opens. Access does not open multiple projects on the same Access window; instead, it opens multiple instances of Access. Excel, however, can open various workbooks in the same window. This difference is a critical factor when determining the scope of application-level events.
123
91118c04.qxd:WileyRedTight
124
Part I
■
11/28/07
9:14 PM
Page 124
The Building Blocks for a Successful Customization
We’ll start by looking at events for Excel and then move on to Access. As you have already seen in Word, if you plan to deploy application-level event, you need to use a class module. Thus, all examples here use a class module. To practice these examples, you should use the standard name clsEvents, as it is the name we will be referencing throughout our processes. The first example demonstrates how to monitor the printing of a particular workbook and only allow the specified workbook to print. Start by opening Excel and adding a new class module. Insert the following code: Public WithEvents appXL As Excel.Application Private Sub appXL_WorkbookBeforePrint(ByVal Wb As Workbook, _ Cancel As Boolean) If Not Wb.Name = “AllowedWorkbook.xslm” Then MsgBox “Printing is not allowed, except for the “ _ & “AllowedWorkbook.xslm workbook”, vbCritical Cancel = True End If End Sub
Once you have created the preceding code, you can use the local Open event to set this application-level event: Dim mappXL As New clsEvents Private Sub Workbook_Open() Set mappXL.appXL = Application End Sub
Once this has been set, only the allowed workbook will print. All other printing attempts will be canceled, and the message shown in Figure 4-12 will be displayed.
Figure 4-12: Message box triggered by printing event
Now let’s look at an example using Access. The need for data validation is fairly universal, so we’ll use an example that provides one way to validate data before it is accepted into a table. In this scenario, assume a text box is used to enter data into a field that should only contain positive values. Therefore, if the text box appears on multiple forms, you might be tempted to write validation code for each occurrence of the text box. However, you can save a lot of time by writing a simple procedure within a class module and then using it throughout the project.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 125
Introducing Visual Basic for Applications (VBA)
Start by creating the class module shown below. Although this has some new material, you should recognize the terms that we’ve previously used; and because we are using standard naming conventions, you can probably discern the rest. Read through and interpret the code, and then review the explanation that follows: Public WithEvents clsTextBox As TextBox Private Sub clsTextBox_AfterUpdate() With Me If .clsTextBox.Value < 0 Then MsgBox “Negative values are not allowed in this field...”, _ vbCritical .clsTextBox.Value = 0 End If End With End Sub
N OT E The code example does not test for text typed into the textbox control. Now that the class module has been created, you need to call it from any form that will use this text box. Note that the text box must have the same name on every form. The steps will be the same for each form with the text box, so open an applicable form in Design View and enter the following code: Dim mtxtbox As New clsTextBoxEvents Private Sub Form_Load() Set mtxtbox.clsTextBox = Me.txtValue End Sub Private Sub txtValue_AfterUpdate() ‘ This procedure has no use. ‘ It only serves to ensure the class is executed. End Sub
The TextBox instance is set when the form is loaded. The AfterUpdate event, which would normally contain the code that is now in the class module, is now used only to subclass (trigger) the event. In other words, if you do not have the AfterUpdate event signature (or whatever event signature you plan to use) present in the code module of your form, the public event declared in the class is not triggered. You should now be able to reuse the class anywhere in your project without having to write the same code repeatedly. This is a very useful technique that can be applied to many scenarios.
The Object Browser As no doubt you’ve come to realize, each object model has hundreds of member properties and methods. This can seem overwhelming and make you wonder how you’re supposed to remember them all. Thankfully, you don’t need to know them off the top of your head. However, you certainly need to know where to go in order to get some help and to find out what objects, properties, and methods are available.
125
91118c04.qxd:WileyRedTight
126
Part I
■
11/28/07
9:14 PM
Page 126
The Building Blocks for a Successful Customization
The best place to find such help is in the Object Browser, where you will be able to inspect each object and its corresponding members. To work with the Object Browser, follows these steps: 1. Open the VB editor (Alt+F11). 2. Press F2 (or go to View ➪ Object Browser). Figure 4-13 shows the Object Browser open to all libraries.
Figure 4-13: Inspecting libraries using the Object Browser
From the Project/Library drop-down list, you can filter which library to show. Figure 4-13 shows all libraries available in the project, but if you had referenced a library, then you could filter to see only the classes belonging to that library. You could then review the members of each class by selecting the class you want to inspect. (See the following section for information about how to reference libraries.) When you finally select a member of a class, the Object Browser will provide information about that member. In this particular case, as you can see from the details pane at the bottom of the window, Item is a property, and it is a member of Excel and of the Areas class.
Referencing Libraries In many cases, you will find yourself in a situation where you must reference other libraries. For example, you might want to use PDF objects to read PDF files from your application or to write PDF files. In such cases, you would not need to reinvent the wheel in order to create links to other programs. All you need to do is reference the appropriate library. Obviously, you would still need to know how to use the library, but once the library is referenced you can inspect its object model.
91118c04.qxd:WileyRedTight
11/28/07
9:14 PM
Chapter 4
■
Page 127
Introducing Visual Basic for Applications (VBA)
This probably sounds a little more complicated and intimidating than it really is. Actually, it is very simple to reference a library — as long as it is installed on your computer or at least accessible to the computer. Just follow these steps: 1. Open the VB editor (Alt+F11). 2. Go to Tools ➪ References. 3. When the reference dialog box opens, scroll through the list to find the desired reference, check the box in front of it, and then click OK to finish. You can now use the Object Browser to inspect the library that you just referenced (see Figure 4-14). From the Project/Library drop-down, choose the library that you just installed.
Figure 4-14: Referenced library viewed in the Object Browser
In this case, we have referenced the Outlook library. That’s all it takes to make Outlook’s objects available to your Excel, Access, or Word project. You can now do such things as save a report, attach it to an e-mail, and have it waiting for the user to complete the address block. In addition, you can further automate the process to automatically fill in the address line and send the e-mail — a routine that can be quite helpful in obtaining error reports without imposing on the user. Keep in mind that just because you reference a library in one application, that does not mean that it is referenced in the others. If you want to work with PDF files in all three applications, you need to set the reference in all three. Moreover, reference settings do not travel with the files from one computer to another. Although there are certain scenarios in which references can be set at the project level and essentially installed on the user’s machine, that is beyond the scope of what we cover here. Nevertheless, knowing that it is possible might avoid some confusion and frustration if you see different libraries referenced. We’ve only provided a glimpse at how powerful and versatile VBA can be, and although you’re probably intrigued and want to explore more right now, we have to
127
91118c04.qxd:WileyRedTight
128
Part I
■
11/28/07
9:14 PM
Page 128
The Building Blocks for a Successful Customization
stay focused on covering all the fundamentals so that you will be prepared for customizing the Ribbon. So now we’ll move on to figuring out why code doesn’t always work the way we want it to.
Early and Late Bindings Explained When working with external objects — that is, objects that do not belong to the object model you are working with — you must create an instance of that external object. You do that with either early or late binding. When you install references to other libraries you work with early binding. This means you bind the objects you dimension in your code to match those of the library you referenced, as shown in the following example: Dim olEmail
As Outlook.MailItem
Using the referenced Outlook library shown in Figure 4-14, you can dimension an Outlook e-mail object from either Excel, Access, or Word. Early binding not only speeds up the programming process, as you now have all the library’s objects exposed to you, along with all of their properties and methods, but it also improves code performance. The drawback is that users of your project will need the library registered in their installation of Office as well. Late binding, conversely, does not rely on referencing. You simply declare generic objects and use their properties and methods, and these will be resolved at runtime (when that part of the code is running). Following is an example: Dim Dim Set Set
appOL As Object Email As Object appOL = CreateObject(“Outlook.Application”) Email = appOL.CreateItem(olMailItem)
The preceding example does a late binding of the appOL object (representing the Outlook application) and then does the same with the Email object (representing an Outlook mail item). Because these are generic objects, they can be used to represent any object you like. The CreateObject function will always create a new instance of the object you want — that is, if you have an instance of the object already opened, then another one is created (this is not the case with applications that only run one instance of the object at any given time, such as Outlook). If you do not want to use up resources by creating new instances of an application object, you can use the GetObject function instead: Set appWrd = GetObject(, “Word.Application”)
The preceding example will fetch the Word application object if it is already open so that you do not need to create another instance of the application. Of course, if the
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 129
Introducing Visual Basic for Applications (VBA)
instance of the application doesn’t exist, then the code will return an error. You can get the best of both situations and avoid the error with the following code: On Error Resume Next Set appWrd = GetObject(, “ Word.Application”) If appWrd Is Nothing Then _ Set appWrd = CreateObject(“Word.Application”)
You use On Error Resume Next in order to ignore the possible error of not having an instance already running. If the error occurs, then the object is not set and it remains as “nothing”. You check whether it is nothing and if it is, you create a new instance of the object. For more on error handling, see “Error Handling” later in this chapter. At this point, we will turn our attention to debugging code.
Debugging Your Code More often than not, users spend a lot of time trying to figure out why a certain piece of code does not behave as expected. When you write code, it is interpreted according to rules of logic, not according to what is perceived as truth. This is where a lot of developers get bogged down. There is no a priori correlation between the logic of your code and what you consider to be true, which has no bearing on the result; rather, the result is completely based on the premises set out in your code. Therefore, if the premises of the code are true, the result of running the code will also be true. However, this is not at all the same as saying that the code is true (that the code will perform as written) and the desired result of running the code is true (will create the desired/expected result). Sound confusing? Actually, it doesn’t have to be; but the more complex the code, the more confusing it can become. And that’s when debugging comes into play. The following syllogism will help illustrate what is meant by following logically to a conclusion, rather than trying to figure out what the truth is: All Excel users are weirdos. My pet snake is an Excel user. Conclusion: My pet snake is a weirdo. Obviously, we know that not all Excel users are weirdos, let alone that my pet snake is an Excel user. However, if you were to put this into your code, the logic dictates that you must accept the conclusion (the result) that can be drawn from the first two premises; therefore, the code would provide the logical conclusion and state that my pet snake is also a weirdo. This is exactly what code will always do, no matter what you think or know about Excel users, or whether you believe that my pet snake is a fingerless but competent Excel user.
129
91118c04.qxd:WileyRedTight
130
Part I
■
11/28/07
9:15 PM
Page 130
The Building Blocks for a Successful Customization
In other words, what you think about the truth of the premises has no bearing on the conclusion itself because the conclusion flows directly from the premises. Once you’re committed to the premises, you must accept the conclusion. The preceding example could be written as an If...Then statement. In doing so, you will quickly realize why it is critical to state the premises correctly in order to achieve the desired results. This section explains how to debug your code. Debugging code is about checking the logic of your code to ensure that it performs as expected. Sometimes, even after a lot of debugging, you will still find bugs, even after months of running smoothly. That’s just part of the dynamics of programming and the never-ending changes in programs and the ways users do things. You will definitely benefit from learning how to debug and troubleshoot code.
Debug.Print and Debug.Assert The Debug object is used to send output to the Immediate Window (see Figure 4-15 and the “Immediate Window” section later in this chapter). It has two methods: ■■
Print: This method is used to print some sort of output to the Immediate Window.
■■
Assert: This method is used to assert a condition that evaluates to False at runtime when the expected result should evaluate to True.
The first method is very useful when you need to see the value a variable has taken or how this value behaves during the course of executing the code: Sub debugPrint() Dim x As Long Randomize x = Int((100 - 0 + 1) * Rnd + 0) Debug.Print “The value for x is: “ & x End Sub
This is a simple example that will generate a random number between 0 and 100 and display the result in the Immediate Window. This little bit of code could easily be used for selecting random winners, to draw numbers for a bingo, and a myriad of other purposes. The Assert method can come in handy when you have a logical situation to evaluate, such as the syllogism involving the pet snake. Although we can all agree that the scenario is absurd, the point is that the conclusion is logical given the premises. You might be wondering at this point what would happen when you write code that should return a true value or is based on true assumptions, but actually returns a false value. You can assert the code so that when it is evaluated to false, it stops at the assertion line, as shown in the following example: Sub debugAssert() Dim lngRow Dim blnAssertNotEmpty
As Long As Boolean
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 131
Introducing Visual Basic for Applications (VBA)
lngRow = 1 Do Until IsEmpty(Cells(lngRow, 1)) blnAssertNotEmpty = Not (IsEmpty(Cells(lngRow + 1, 1))) lngRow = lngRow + 1 Debug.Assert blnAssertNotEmpty Loop End Sub
In this example, you loop through the specified cells in a worksheet checking whether the next one is empty or not. When an empty cell is encountered, the Boolean value becomes False and the code stops at the assertion line.
Stop Statement The Stop statement is used to suspend execution of code at a particular point. Suppose you need to stop the code when the workbook, document, report, or form is opened. You could add a MsgBox and enter the code after the message is shown. However, that may create unwanted interruptions, as you would have to deal with the message box and the code every time the line is executed. A better alternative may be to use the Stop statement — and avoid the message box. Using a Stop statement will suspend execution, enabling you to analyze your code. The following code includes a Stop statement that halts processes before you set the Ribbon object. If you stopped the execution at this point, then the UI will still load, but the Ribbon object would not be available to work with: Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Stop Set grxIRibbonUI = ribbon End Sub
Notice that using Stop has a similar effect to adding a breakpoint to your code. However, you would not be able to add a breakpoint to closed files. Thus, when the file is closed, the breakpoint would lose its effect. The Stop statement, however, ensures that the code is suspended at the precise location you’ve set. Therefore, every time the file is opened, the process stops at that point, enabling you to check the condition and correctness of variables and other processes in your code. If you already have the file open, another alternative is to use breakpoints. These also stop the code, and they enable you to step through the code to see the effects, one line at a time. To add a breakpoint, go to the line where you want to add a break/stop and then press F9 (or go to Debug ➪ Toggle Breakpoint). The code will now stop when it reaches that point. As you can see from the toggle option, it is just as easy to remove the breakpoint.
131
91118c04.qxd:WileyRedTight
132
Part I
■
11/28/07
9:15 PM
Page 132
The Building Blocks for a Successful Customization
Immediate Window The Immediate Window, shown in Figure 4-15, is an inconspicuous part of the VBE but it is an invaluable tool for debugging code. Many developers use the Immediate Window to display a value, but it can do so much more, including the following: ■■
Test code you’ve written or debug problems in your code.
■■
Query or change the value of a variable.
■■
Query or change the value of a variable while execution is halted.
■■
Assign the variable a new value.
■■
Query or change a property value.
■■
Run subprocedures or functions.
■■
View debugging output.
Figure 4-15: The Immediate Window
If you recall, this is the window where we sent our x variable earlier in this section. Additional ways to open the Immediate Window include going to View ➪ Immediate Window or pressing Ctrl+G. You have already learned how to send variable values to the Immediate Window so that you can have a look at them. However, you may encounter scenarios where you actually do not want to flood the Immediate Window with all sorts of variable values, lists, and the like. Study the following piece of code, and you’ll understand what we mean: Sub immediateWindow() Dim i As Integer Dim blnAssert As Boolean For i = 1 To 10 If i Mod 2 = 0 Then blnAssert = False Else: blnAssert = True End If Debug.Assert blnAssert Next i End Sub
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 133
Introducing Visual Basic for Applications (VBA)
Here, the Assert method is being used to stop the code, although a more realistic scenario might have halted the code for some other reason, such as when a modal dialog box is shown. What matters here is only that the code halted, so now you need to find out what the value of i is at that specific point. Figure 4-16 shows how to use the Immediate Window to query the value assigned to a variable at the point where the code stopped.
Figure 4-16: Querying a variable value in the Immediate Window
At this point, all you need to do is type ?i (or the name of any other variable) and press Enter. The Immediate window will give you the value of the variable at the point where the Assert method stopped the code. You’re now probably thinking, “So what?” Well, the Immediate Window’s usefulness does not stop there. You can also evaluate expressions, such as comparing the variable to a set value or to another variable. The following expression compares the current value of i to the value of the variable j: ?i=j
This will return true if the expression is equal (if i indeed equals j); otherwise, it returns false. You could also type the following query into the Immediate Window to retrieve the entire connection string of an Access database. The results are shown in Figure 4-17. ?CurrentProject.Connection
Figure 4-17: Querying a property in the Immediate Window
Note that you use a question mark before typing anything. This question mark means you are querying something. In the previous examples, we have queried the value of a variable, the result of a comparison, and the connection string of an Access database.
133
91118c04.qxd:WileyRedTight
134
Part I
■
11/28/07
9:15 PM
Page 134
The Building Blocks for a Successful Customization
It does not have to stop there. Suppose you wanted to query the result of a custom function. In Word, you can query the result of your custom function as follows: ?ThisDocument.myCustomFunction
You can do something similar with Excel: ?RibbonXFunctions.hasSuperTip (“rxbtnDemo”)
With Access, you could use the following: ?IsLoaded(“myForm”)
What if it were a subprocedure instead of a function? Not a problem — all you have to do is remove the question mark to call the procedure and display the results in the Immediate Window. Note that in the case of Word and Excel you explicitly identify where the function is located (ThisDocument and RibboXFunctions standard module). It makes life simpler if you do this, because then you can clearly refer to the location of the subprocedure.
Locals Window The Locals Window is used to show variable values as well as other objects that belong to the procedure being executed. If it isn’t currently showing, you can open the Locals Window (shown in Figure 4-18), by going to View ➪ Locals Window.
Figure 4-18: Locals Window
From Figure 4-18, you can identify the expression, its type, and the current value, as described here: ■■
The Me object, which refers to ThisDocument
■■
The i variable, which takes a value equal to 2 and is an Integer data type
■■
The blnAssert variable, which takes a false value and is a Boolean data type
■■
The j variable, which takes a value equal to 2 and is an Integer data type
As you can see, the Locals Window will show you the values of variables as well as objects that come under the procedure, so it is a very handy way to view how variables behave as each line of code is executed.
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 135
Introducing Visual Basic for Applications (VBA)
If you have several variables, values, or objects to monitor, using the Locals window is probably one of the best ways to do so.
Watches Window The Watches Window is another great tool for debugging code. It enables you to specify instructions for watching the value of an object, such as a variable, an expression, a function call, and so on. Similar to some of the prior examples, a watch will pause code execution when the criterion is met (such as when the expression is true) or the variable that is being watched changes. If the Watches Window, shown in Figure 4-19, is not yet displayed, you can open it from View ➪ Watch Window.
Figure 4-19: The Watches Window
Now that you have the Watches Window open, you need to specify a watch. You do that with the Add Watch dialog box, shown in Figure 4-20. To open the Add Watch dialog box, do one of the following: ■■
Right-click on the Watches Window and choose Add Watch from the pop-up menu.
■■
Go to Debug ➪ Add Watch.
Figure 4-20: The Add Watch dialog box
135
91118c04.qxd:WileyRedTight
136
Part I
■
11/28/07
9:15 PM
Page 136
The Building Blocks for a Successful Customization
Once you’ve set up a watch, every time an expression meets the criterion or a variable changes, the watch will be listed in the Watches Window, as shown in Figure 4-21.
T I P You might find it easier to right-click on the variable and then select Add Watch from the pop-up menu. Table 4-11 shows the expressions available in the Add Watch dialog box and explains how each of the expressions is used. Table 4-11: Add Watch Dialog Elements ELEMENT
DESCRIPTION
Expression
Refers to the expression to be watched. It can be a variable, a property, a function call, or any valid expression that you may have in mind.
Procedure
Refers to the procedure where the term is located. By default, it shows the details for the selected term in the Watches Window. You can choose all procedures or just a specific one.
Module
Refers to the module where the term is located. By default, it shows the details for the selected term in the Watches Window. You can choose all modules or just a specific one.
Project
Shows the name of the current project. You cannot evaluate expressions that are outside the current project context.
Watch expression
Shows the watch expression and its value in the Watches Window. When its value changes, the result is updated in the Watches Window.
Break When Value Is True
Code execution will break when the expression evaluates to true or is non-zero (does not apply to strings).
Break When Value Changes
Code execution will break when the value of the expression changes within the defined context.
Now that you have all the explanations, the following example creates a simple watch to monitor a string (ensure that the Watches window is visible): Sub watchT() Dim strT As String strT = “This is a T-string” strT = “This is no longer a T-string” End Sub
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 137
Introducing Visual Basic for Applications (VBA)
T I P As well as typing your expression in the Add Watch dialog box, you can also drag and drop it onto the Watches Window. If you drop in an expression that is a value, VBE will take care of the rest for you. Otherwise, you will get an error message indicating the problem. In setting up the watch, choose Break When Value Changes so that the code execution enters break mode when the string changes from the first to the second. The code and Watches Window are shown in Figure 4-21.
Figure 4-21: Watching strT for a change in value
N OT E When selecting a context, you should narrow the scope so that you do not include procedures or modules that are irrelevant to the specific action or watch. This will improve performance, as there will be less to watch during the execution process.
Error Handling As you write more and more code, you will realize that it is virtually impossible to predict everything that could happen. You could come across overflows, infinite loops, locked tables, and so on. All of these will generate some sort of error, many of which are almost impossible to predict. In order to ensure that your code does not crash the program, you need to add error handling. There are some very simple ways to tackle unresolved or unpredictable problems that the end user may encounter. In this section, we cover some of the basics of error handling. In addition to enabling you to deliver more stable projects, it will also help you write more robust code.
137
91118c04.qxd:WileyRedTight
138
Part I
■
11/28/07
9:15 PM
Page 138
The Building Blocks for a Successful Customization
On Error Resume Next Maybe the simplest and most frequently used error handler for VBA is On Error Resume Next. In lay terms, what the instruction does is simply ignore whatever error happens and continue execution on the next line. The problem with that, of course, is that the error has not been handled. It has been ignored, which can have implications in your code because other parts of the code may be dependent on the line where the error occurred. Consider the following code and then we’ll discuss it. The example applies to Excel, but it can be adapted to Word and Access. Because we’re working with the FileSystem object, you need to set the reference to the Windows Script Host Model before continuing. Sub onErrorResumeNext() Dim fsoObj As FileSystemObject Dim fsoFolder As Folder Dim fsoFile As File Dim lngRow As Long lngRow = 1 On Error Resume Next Set fsoFolder = fsoObj.GetFolder(“C:\YourFolder”) For Each fsoFile In fsoFolder ActiveSheet.Cells(lngRow, 1) = fsoFile.Name Next fsoFile End Sub
The flaw in this code is that the fsoObj has not been set. By instructing VBA to ignore the error and resume processing from the next line, all subsequent errors will also be ignored. The Folder object needs a FileSystem object, so it will fail; and because the File object depends on the Folder object, it will also fail. This causes a chain reaction in the entire code, which means it won’t provide the expected result — i.e., a list of files in YourFolder. As you can see, although On Error Resume Next is useful, it can also cause bigger problems than the error itself. This could have been a critical error that needed proper handling, but if the error is ignored you may be oblivious to its existence. Thus, you should use this method of error handling sparingly and only when you know it will not impair anything critical in your project. A better option for handling exceptions in code is to direct them to an error-handling process, the subject of the next section.
On Error GoTo This form of error handling can appear in the form of On Error GoTo 0 or On Error GoTo , where stands for a label of your choice. You will typically find that most people use labels such as ErrHandler or Err_Handler ( merely repre-
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 139
Introducing Visual Basic for Applications (VBA)
sents the name a procedure or function. In this case, that might be ErrHandler, Err_Handler, or something meaningful to the purpose of the instruction). The point is to clearly communicate that if the error occurs, the process must move to the code that handles the error rather than continuing with the next line of code. The first option (On Error GoTo 0) is VBA’s default error handling. It will simply return the usual VBA error message (refer to Figure 4-5 to refresh your memory regarding what it looks like). Essentially, with that message, the user has two options: choose End to close the program and hope that all data is preserved and the file works when reopened, or choose Debug and be faced with the code window with the failed line of code highlighted. Since neither of these options is very appealing, you can appreciate why effective error handling is so important.
N OT E On Error GoTo 0 does not mean that error handling begins at line 0 of your code. Rather, it is akin to having no error handling enabled in your code (error handling is disabled). Even if you have other error handlers in the procedure, once this line is triggered it is essentially the end of the road, and the default message appears. Or, worse yet, the program just shuts down after indicating that it encountered a problem. Generally speaking, error handling is written in the following format. We’ll review the parts and then go through a working example. Sub onErrorGoTo() On Error GoTo Err_Handler ‘insert body of procedure/function here Exit Sub Err_Handler: ‘insert error handling code here Resume Next End Sub
Notice that before the Err_Handler label we place an Exit Sub instruction. This is necessary because without this line, the error handling code will be executed as part of the procedure. You’ll also notice that we place a Resume Next after the error handling procedure. This implies that the code should be resume with whatever would otherwise have happened after the line where the error occurred. You could use Resume to send the code to another label within the code that handles something else, such as cleaning up objects.
N OT E When handling errors in a function, use Exit
Function. When handling
errors in a property, use Exit Property.
In order to practice this, go back to the example of the previous section and add error handling code as follows:
139
91118c04.qxd:WileyRedTight
140
Part I
■
11/28/07
9:15 PM
Page 140
The Building Blocks for a Successful Customization
Sub onErrorResumeNext() Dim fsoObj As FileSystemObject Dim fsoFolder As Folder Dim fsoFile As File Dim lngRow As Long lngRow = 1 On Error GoTo Err_Handler Set fsoFolder = fsoObj.GetFolder(“C:\MyFolder”) For Each fsoFile In fsoFolder ActiveSheet.Cells(lngRow, 1) = fsoFile.Name Next fsoFile Set fsoObj = Nothing Set fsoFolder = Nothing Exit Sub Err_Handler: MsgBox “The following error occurred: “ & vbCr & _ Err.Description, vbCritical, “Err number: “ & Err.Number Resume Next End Sub
As the errors now occur, you will be able to analyze each one and then take appropriate measures to correct it. Because the errors may occur with the end user of your project, you may also want to devise a means to log the errors. In addition, as mentioned earlier, it can also be helpful to silently e-mail the error notices or log files to a designated recipient. Having a log enables you to track errors and improve error trapping, and it strengthens your ability to fix bugs. Moreover, it can be an invaluable tool for troubleshooting and delivering more stable and robust solutions.
Working with Arrays Because you may also use arrays in your customizations, we’ll provide a brief introduction. An array is basically a group of indexed data that VBA treats as single variable. If that sounds confusing, then take a look at the following declaration, and you’ll start to get the picture: Dim InflationRate(2000 To 2007) As Double
The indexes in this case are the years between 2000 and 2007, and the data we would be interested in is the inflation rate. You can assign and retrieve values from an array as follows: Sub ExampleArray() Dim InflationRate(2000 To 2007) As Double
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 141
Introducing Visual Basic for Applications (VBA)
InflationRate(2000) = 0.043 Debug.Print InflationRate(2000) Debug.Print InflationRate(2005) End Sub
In this exercise, we assign the imaginary inflation rate of 4.3% for the year 2000. We then print the values for the years 2000 and 2005 to the Immediate Window. Because no inflation rate is assigned to the year 2005, the value returned is zero (be careful here; otherwise, you might think that the inflation rate for 2005 was actually zero). Notice that although there are several inflation rates, there is only one variable InflationRate. Although we used a Double data type, arrays can use other data types — for example, when referring to objects. The next example demonstrates how to use an array that contains worksheet objects: Sub ExampleArray2() Dim MyWorksheet(1 To 2) As Worksheet Set MyWorksheet(1) = ThisWorkbook.Sheets(1) Set MyWorksheet(2) = ThisWorkbook.Sheets(3) Debug.Print MyWorksheet(1).Name Debug.Print MyWorksheet(2).Name End Sub
The second index of our array (MyWorksheet(2)) does not match the index of the sheet to which it refers (ThisWorkbook.Sheets(3)). This illustrates that items can go into the array in whatever order you choose, but it is critical to know where they are so that you can call the correct object.
Determining the Boundaries of an Array Suppose you have an array but you don’t know its lower or upper limits. In that case, you can use the LBound and UBound functions to determine the lower bound and upper bound of the array. Using the inflation rate array we created earlier, you can check the upper bound as follows: MsgBox UBound(InflationRate)
In this particular example, you would get 2007 as the upper bound. This is substantiated by the settings in code (2000 to 2007), but in most cases the limits will not be so obvious, so you can use the two handy functions to quickly retrieve the answers.
141
91118c04.qxd:WileyRedTight
142
Part I
■
11/28/07
9:15 PM
Page 142
The Building Blocks for a Successful Customization
Resizing Arrays Looking again at the inflation rate example, you’ll see that we declared an array which has a fixed size — that is, you can only use the indexes specified to fill the array with data. An alternative is to create a dynamic array that can be resized later. You use the ReDim keyword to resize an array, as shown in the following example: Sub ExampleArray(ByVal StartingYear As Long, _ ByVal EndingYear As Long) Dim InflationRate() As Double ReDim InflationRate(StartingYear To EndingYear) Debug.Print LBound(InflationRate) Debug.Print UBound(InflationRate) End Sub
The problem with this approach is that if the array contains any data already, then that data will be cleared. If you want to keep the previous information contained in the array, you can use the Preserve keyword alongside the ReDim keyword, like so: ReDim Preserve InflationRate(StartingYear To EndingYear)
Here, you set the lower and upper bounds of the array without losing existing data. Now you can add items to the array by changing its upper limit. What you cannot do is change its lower bound. Rather than having to build a new array, you can pass the original array to a variant data type, redimension the array (clearing all its contents), and finally pass back the information from the variant data type. We’ll do that with our final example: Dim InflationRate() As Double Dim varArray As Variant Sub RunExampleArray() Call ExampleArray(1990, 2000) Call ExampleArray2(1980, 2007) End Sub Sub ExampleArray(ByVal StartingYear As Long, _ ByVal EndingYear As Long) ReDim InflationRate(StartingYear To EndingYear) InflationRate(1990) = 2.4 InflationRate(2000) = 1.9 varArray = InflationRate()
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Chapter 4
■
Page 143
Introducing Visual Basic for Applications (VBA)
End Sub Sub ExampleArray2(ByVal StartingYear As Long, _ ByVal EndingYear As Long) ReDim InflationRate(StartingYear To EndingYear) InflationRate(1990) = varArray(1990) InflationRate(2000) = varArray(2000)
Debug.Print LBound(InflationRate) Debug.Print UBound(InflationRate) Debug.Print InflationRate(1990) Debug.Print InflationRate(2000) End Sub
Notice that both variables used in this example are declared in the general declaration area of the module, so your data is back in the new array, and you are set to work with data from 1980 through 2007, instead of the original array from 1990 to 2000.
Conclusion This has been a very long chapter, and although we have introduced many aspects of VBA, we have barely scratched its surface. We will return to VBA in Chapter 12, when you are introduced to some more advanced concepts regarding VBA programming. However, from this point forward, our work with VBA will be fully integrated with the Ribbon customization process. It is hard to determine what requires a solid understanding and what does not, as many things can be useful in a certain context and not in another. Our goal with this chapter was to cover the fundamentals of VBA, while emphasizing the things that we will use throughout the book. With that in mind, we encourage you to pay particular attention to working with decision statements such as If...Then...Else and Select Case. You will also need to be comfortable with the various ways you can loop through objects and handle events. Finally, of course, you will find that debugging and error handling are critical skills. As we just demonstrated, error handling is particularly important because you do not want your customization bombing for the end user. We cannot overemphasize that programs will change and users will do unexpected things, so you have to test beyond your own routines; and if you have any doubts, go back, revise, and practice. Now that you’ve learned the basics for XML and VBA, you are ready for Chapter 5, where you will learn the nuts and bolts of callbacks.
143
91118c04.qxd:WileyRedTight
11/28/07
9:15 PM
Page 144
91118c05.qxd:WileyRedTight
11/28/07
9:15 PM
Page 145
CHAPTER
5 Callbacks: The Key to Adding Functionality to Your Custom UI
If you’ve worked through the earlier chapters, you already know some of the basics relating to Ribbon customization, such as XML structure, and you’ve had an introduction to VBA. This chapter introduces you to callbacks — the code that makes your customization work. Without callbacks, your UI may look beautiful, but that’s all it will be: just a pretty Ribbon — that is, of course, unless you are drawing from built-in controls, as they don’t require custom callbacks. However, for custom controls, looks just aren’t enough. What really matters is that your UI adds value for the user. In this chapter you learn the basics about how callbacks provide the functionality required for custom controls to work. In the following pages you learn a combination of XML and VBA, as you will need to specify a callback in your XML code and then write VBA code to match and handle that callback. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
Callbacks: What They Are and Why You Need Them Callbacks are subprocedures and functions used by your custom UI to make your customization work. A callback simply represents the movement of the instruction given. For example, when you set the onAction attribute for a button and load the UI, once the button is clicked it generates a callback on the action specified in the attribute.
145
91118c05.qxd:WileyRedTight
146
Part I
■
11/28/07
9:15 PM
Page 146
The Building Blocks for a Successful Customization
If nothing is found, then the callback fails because it has an exception in the code: In other words, the callback specified in the attribute does not exist in VBA, so it fails. Figure 5-1 shows how the callback flows from the UI project to the VBA code project, in much the same way that an event calls a procedure or function.
Figure 5-1: Callback flow
Your UI will call on your VBA project as soon as it loads, searching for the specified functions or procedures. If they are found, then the value is passed back to UI. If the specified functions or procedures are not found, then an error results. There are two primary ways to create callbacks: ■■
Type the procedure or function directly into a standard module in the VB editor.
■■
Generate the procedures using a tool such as the Office 2007 CustomUI Editor.
The key advantage of using tools such as the Office 2007 CustomUI Editor is that they sweep the XML code and return a callback signature, also known as a subprocedure stub, for each attribute in the XML code that has a callback needing handling. Attributes that take a callback as a value include onAction, getVisible, and getImage. In addition to saving time, using the custom editor also helps you avoid mistakes that can easily be made when manually writing callback signatures. Each of the preceding attributes generates a different callback signature that must be handled using code in order for the UI to function properly. This book relies on VBA, but you can use other code languages, such as C#. Another thing to keep in mind is that some callbacks are called upon, and need to run, when the project loads. This means you will get an error message if the callback handler (the VBA code that responds to the callback) is not present in the project. However, there is no need to panic if that happens, as you will learn how to mitigate this type of error. More important, you will learn how to avoid them altogether.
Setting Up the File for Dynamic Callbacks In this section you learn some of the basic techniques that are fundamental to all customizations. One of the critical elements is that the file must be macro-enabled in order for custom controls to work. In fact, a file must be macro-enabled, otherwise you will not be able to add or run VBA code.
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 147
Callbacks: The Key to Adding Functionality to Your Custom UI 147
The macro-enabled constraint applies to Excel and Word only. Access has another constraint, addressed later in this chapter in the section “Handling Callbacks in Access.”
Capturing the IRibbonUI Object An important part of the customization is related to the IRibbonUI object. This object refers to the Ribbon user interface and can be used to control how things respond. One of its key uses in VBA is to invalidate the entire Ribbon object (so that certain features of the Ribbon can be changed) or to invalidate specific controls in the Ribbon (so that certain features of the control can be changed).
C R O S S-R E F E R E N C E See “Invalidating UI Components” later in this chapter for more information about invalidating the Ribbon and its components.
Adjusting the XML to Include onLoad In order to use the IRibbonUI object, you need to set it in VBA. We’ll explain more about that later, but for now we’re going to stay focused on how you get to that stage. First, you need to specify a value for the onLoad attribute of the UI. That is done by specifying a callback for the onLoad attribute, as shown here:
Because the value of the onLoad attribute is a callback, it needs to be handled in the VBA code. This, in turn, enables you to use the IRibbonUI object in your project.
Setting Up VBA Code to Handle the onLoad Event You likely noticed that we previously referred to onLoad as an attribute, but are now calling it an event. This is because you can define values for a number of attributes, such as onAction, getLabel, and onLoad, in the XML file. Once a value has been assigned to an attribute, if it can be triggered in any way it will cause an event to occur, and thus we make the distinction between attribute and event. Because the IRibbonUI object is used throughout the application, it needs to be declared in the Global Declarations area of a standard module, as shown in the following code. Recall from Chapter 4 that the g prefix indicates a global variable. We have adopted and built upon the Reddick naming conventions, but you can implement whatever standard you choose. The critical part here is that global variables must be declared at the beginning of the module, before any other type of variable or any code. ‘The global ribbon object Dim grxIRibbonUI As IRibbonUI
91118c05.qxd:WileyRedTight
148
Part I
■
11/28/07
9:15 PM
Page 148
The Building Blocks for a Successful Customization
We use the standardized form grxIRibbonUI to refer to the IRibbonUI object. The next step is to add a callback to set the object: Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set grxIRibbonUI = ribbon End Sub
C R O S S-R E F E R E N C E There are other ways to set custom properties of the Ribbon object. Chapter 12 has additional examples and instructions.
After you have globally set the object, it will be available throughout the project. Note, however, that the Ribbon object is very “sensitive” to change. This means that if you step into the code at any time to make changes, the object instance will be lost and any operation requiring the Ribbon object will fail. Therefore, it isn’t just as simple as test, tweak, test — you need to save, close, and reopen the project any time you make a change.
Generating Your First Callback The previous topic explained how to set the IRibbonUI object, and since you know that you need to get a callback signature, you’re probably wondering how you’re supposed to know which signature to use. That’s the next step in the process, so we’ll explain that now. A toggleButton, for example, would have the following signature: Sub rxtgl_Click(control as IRibbonControl, pressed as Boolean)
A generic button would have this signature: Sub rxbtn_Click(control as IRibbonControl)
You have already seen that the onLoad attribute generates a callback with a different signature. We realize that this may seem like a lot of minor nuances, so the goal of this chapter is to make it perfectly clear how all these details affect the Ribbon. This is a complex subject with endless variations, but the more you work through it, the better you’ll understand it.
Writing Your Callback from Scratch To learn how to write a callback from scratch, begin by taking a look at the previous example using a callback signature for a toggleButton: Sub rxtgl_Click(control as IRibbonControl, pressed as Boolean)
This seems pretty straightforward , but it is not the entire story. As with most code, there is a lot more to it than what meets the eye. For example, if you happen to know
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 149
Callbacks: The Key to Adding Functionality to Your Custom UI 149
the callback signature, you do not necessarily need to declare the arguments using the standard form given above. The following example helps to demonstrate this point. Suppose you have a toggleButton that uses the following XML:
You could then write the rxtgl_click callback as follows: Sub rxtgl_Click(rxctl As IRibbonControl, toggled As Boolean) If toggled Then MsgBox “I am toggled...And my ID is “ & rxctl.ID, vbInformation End If End Sub
Because the click on the toggleButton triggers the callback, the arguments are passed as usual; hence, the change in the signature will not cause the procedure to fail as long as you have the correct arguments of the correct type. Yes, “correct arguments of the correct type” sounds a bit like double-talk. This is a time when examples work best. In the example, the arguments are used to identify the specific controls: rxctl As IRibbonControl and toggled As Boolean. Notice that the initial As keyword identifies the control. Next, As is used with the argument type to specify that the value of the control must be a certain data type. In this case the toggleButton will require a Boolean value.
C R O S S-R E F E R E N C E If you need a refresher on data types, refer to the section “Data Types” in Chapter 4.
With this under your belt, you could also change the onLoad callback signature to suit your needs, as shown next. This callback sets the Ribbon object in the same way the standard signature does: Sub rxIRibbonUI_onLoad(MyRibbon As IRibbonUI) Set grxIRibbonUI = MyRibbon End Sub
The problem you may have now is that your code differs from the standard format, so it may not be easily understood by other developers; indeed, it may seem foreign to you in a few weeks or months. Therefore, even though it is good to have options, you may find that it is better to stick with the standard format. Table 5-1 lists some callback signatures that will be handy if you choose to write your callbacks from scratch.
91118c05.qxd:WileyRedTight
150
Part I
■
11/28/07
9:15 PM
Page 150
The Building Blocks for a Successful Customization
Table 5-1: Callback Signatures for Different Object Attributes ATTRIBUTE
CALLBACK SIGNATURE
onLoad
(ribbon as IRibbonUI)
getLabel, getPressed, getEnabled, getImage, getScreentip, getVisible, etc
(control as IRibbonControl, ByRef returnedVal)
onAction (for toggleButtons)
(control as IRibbonControl, pressed as Boolean)
onAction (for buttons)
(control as IRibbonControl)
Unless you are a glutton for punishment, we do not advise you to type each signature on your own — just consider the number of callbacks that you need to handle! Of course, you could reduce the amount of work by using shared callbacks, so that several controls are handled by just one callback. That is a subject beyond the scope of this discussion, but you can look forward to learning about shared callbacks later in this chapter, in the section “Using Global Callback Handlers.” An alternative to writing the callbacks is to use a tool such as the CustomUI Editor. This will read the XML code, identify where a callback is necessary, and generate the subprocedure stub, which brings us to the next topic.
Using the Office CustomUI Editor to Generate Callbacks An easy and hassle-free way to generate callbacks is to use the CustomUI Editor. This little wonder-tool will sweep together all of the attributes that return a callback and then generate the callbacks that are needed. The greatest advantage of using this type of editor is that you do not need to keep track of all the callbacks in the XML. This is almost invaluable if the XML code becomes quite long. With the CustomUI Editor, you can use the following steps to automatically generate the necessary callbacks: 1. Use the CustomUI Editor to open the Excel or Word file that contains the XML code. (For an Access file, copy and paste the XML code into a blank CustomUI Editor code window.) 2. Click the Generate Callbacks button. 3. A new Callbacks tab will appear. Highlight all the callbacks that were generated and then copy and paste them into the VBA project.
N OT E Make sure you validate the code before continuing — just click the Validate button on the CustomUI Editor toolbar. Because this does not guarantee a full validation, refer to the section “Showing CustomUI Errors at Load Time” in Chapter 1 for more detailed instructions.
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 151
Callbacks: The Key to Adding Functionality to Your Custom UI 151
Figure 5-2 shows the result of automatically generating callbacks using the CustomUI Editor.
Figure 5-2: The CustomUI Editor generates callbacks on-the-fly.
The preceding example has three callbacks. The first two (onLoad and getLabel) occur when the UI loads. The last callback, onAction, occurs when the button is clicked by the user. The last callback is a shared callback, used by multiple controls that share a common onAction attribute and have a common signature.
Understanding the Order of Events When a File Is Open As you add callbacks to your project, certain procedures will be called when the document is open. Which specific procedures are called varies depending on whether or not your customization has the focus when the project opens. Keep in mind that some procedures are called only when the tab containing the customization has the focus, whereas others are called when the mouse hovers over the control. Understanding the order in which these procedures are called can be tricky because calling order is influenced by numerous variables. However, the event that tops the list is the onLoad event. To help you anticipate the typical order of events, Table 5-2 lists the events for the Ribbon tabs and their corresponding order. Table 5-2: Event Order When Tab Has Focus After Project Is Opened EVENT
TAB GETS FOCUS
TAB HAS FOCUS
ALT KEY IS PRESSED
MOUSE OVER
onLoad
Top most event. It will occur when the UI is loaded.
getVisible
First
First
N/A
N/A
getLabel
Second
Third
N/A
N/A
getImage
Third
Fourth
N/A
N/A Continued
91118c05.qxd:WileyRedTight
152
Part I
■
11/28/07
9:15 PM
Page 152
The Building Blocks for a Successful Customization
Table 5-2 (continued) EVENT
TAB GETS FOCUS
TAB HAS FOCUS
ALT KEY IS PRESSED
MOUSE OVER
getEnabled
Fourth
Second
N/A
N/A
getKeytip
N/A
N/A
First
N/A
getScreentip
N/A
N/A
N/A
First
getSupertip
N/A
N/A
N/A
Second
The preceding table lists only a few examples of common attributes that you might use. You should also keep in mind that order will be affected by the introduction of other attributes, such as getDescription, getTitle, and so on. Nonetheless, the table can serve as a general guide when you plan the best way to tackle the UI in terms of performance.
Can I Have Two Callbacks with the Same Name But Different Signatures? VBA will not allow two callbacks in the same project to have the same name but different signatures. If you try to do this, it will fail, as it would with any other code language. However, the same callback name can have different signatures if the callbacks are in different projects. Therefore, if you have more than one Word and/or Excel document and add-ins opened simultaneously, you might find yourself in an unusual situation that causes a callback return to have an unexpected result. That’s because if there is more than one action (signature) associated with a callback name, then the callback for the active document will run. Because Access uses a different process to manage multiple database instances, we cover that in a separate section of this chapter. The following example will help illustrate how this works in Excel; it is essentially the same in Word. Suppose you have an open Excel workbook with two add-ins installed, and all three items have a custom UI with a control that calls rxbtnnsQaShared. When you click the button on the UI, you expect to add a new workbook. However, the message box shown in Figure 5-3 appears instead.
Figure 5-3: Message box shown instead of adding a new workbook
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 153
Callbacks: The Key to Adding Functionality to Your Custom UI 153
Upon investigating this, you discover that you have used the same callback for all three buttons. Even so, you might assume that if you click the button in the UI, then you should get the response for that specific button, so you add a breakpoint to the VBA to see exactly what is happening with the click event, and then you click the button again. This shows that because your Party Crasher Workbook is active when the button is pressed, Excel runs the code associated with that control. In other words, although we are allowed to use identical names with different signatures as long as they are in different projects, it is not necessarily a good idea (see Figure 5-4).
Figure 5-4: Callbacks with same names in different locations can cause unexpected behavior.
We’ve captured the three callbacks in Figure 5-4. As you can see in the title bars, two of the callbacks are in add-ins and the third is in the Party Crasher workbook. All three are in the same Excel file. You can also run into a similar scenario if you are sharing controls from different documents, as it is likely that they will also have a shared callback, or the same callback name may be repeated in multiple documents. Keep in mind that you will only have a problem if the callback has a different function than the one you are expecting. In general, however, shared callbacks in the same document won’t have an operational impact.
Calling Procedures Located in Different Workbooks In the previous example you saw how having the same procedure name in different files can cause some unexpected behavior depending on which file is active or has the focus.
91118c05.qxd:WileyRedTight
154
Part I
■
11/28/07
9:15 PM
Page 154
The Building Blocks for a Successful Customization
You can also run into a similar problem if your XML code runs VBA that is located in a different workbook. Suppose you have two workbooks: Book1.xlsm and Book2.xlsm. You want to add a button to the first workbook to run a procedure located in the second workbook. You could do that using the following XML code to build the UI:
N OT E Using the BookName.xlsm!rxctl_click structure will cause the CustomUI Editor to struggle with generating the callback. In fact, the callback (shown in the following code) must be modified in order to work. To correct the problem, you merely need to remove the xlsm! Sub xlsm!rxbtnDemo_Click(control as IRibbonControl) End Sub
The preceding code is an incorrectly generated subprocedure stub. As indicated in the note, you merely need to remove the xlsm! and it will work just fine, as demonstrated in the following code sample. We now have the XML in book1.xlsm with the onAction attribute pointing to Book2.xlsm, so when you click the button created by Book1, it will look to Book2.xlsm for the onAction VBA code — so let’s do that. Place the following code in a standard module in Book2: Sub rxbtnDemo_Click(control As IRibbonControl) MsgBox “You called a procedure located at: “ _ & ThisWorkbook.Name End Sub
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 155
Callbacks: The Key to Adding Functionality to Your Custom UI 155
Remember that both workbooks must be open for this to run. If you wanted the onAction attribute to point to a loaded add-in, rather than a workbook, then you would simply prefix the onAction code name with [add-in name].xlam, rather than [workbook name].xlsm. e.g., onAction=”myAddIn.xlam!rxbtnDemo_Click” and then
place the callback VBA code in a standard module in the add-in: Book3.xlam!rxbtnDemo_Click
Keep in mind that this event will run the code contained in the active workbook if both the xlsm and xlam have the same name for a procedure specified under the onAction attribute of the UI. You likely also noticed that although we had to remove the xlxm! from our previous callback, xlam! is required to work with the add-in.
Organizing Your Callbacks As you progress in your Ribbon customization coding skills, you will notice that there are different ways to organize the callbacks. You can have an individual callback handler or you can have a global callback handler that takes care of multiple controls at once. The way you want your VBA code (as well as your XML code) to look will determine which method or combination of methods you use. We start our look at callback organization by working with individual callback handlers.
Individual Callback Handlers When you write XML code, you can specify various attributes that return a callback such as onAction, getLabel, getVisible, getEnabled, and so on. Each of these callbacks has to be handled. The following example illustrates the process by generating three buttons in a group. Although this example is for Word, it also works in Excel. Access is handled differently, so we will work through an Access example later in this chapter.
91118c05.qxd:WileyRedTight
156
Part I
■
11/28/07
9:15 PM
Page 156
The Building Blocks for a Successful Customization
As you can see, each button is assigned its own onAction attribute, which means that each attribute must be handled if you intend to add functionality to the button: Sub rxbtnPaste_Click(control As IRibbonControl) MsgBox “You clicked on “ & control.Tag, vbInformation End Sub Sub rxbtnCopy_Click(control As IRibbonControl) MsgBox “You clicked on “ & control.Tag, vbInformation End Sub Sub rxbtnCut_Click(control As IRibbonControl) MsgBox “You clicked on “ & control.Tag, vbInformation End Sub
As you might anticipate, dealing with each attribute individually can become very cumbersome and hard to maintain. Keep in mind that you need to handle each onAction attribute as well as any other attribute that generates a callback. An alternative to this is to handle multiple attributes at once. As you will appreciate, this is where following a naming convention becomes a blessing. If you’re using our suggested method, you will readily see how easy it is to handle all the callbacks in one tidy process.
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 157
Callbacks: The Key to Adding Functionality to Your Custom UI 157
Using Global Callback Handlers As their name implies, global callback handlers can be used to handle several controls at once, and it gets even better if you standardize the way you name global handlers, because then you can immediately differentiate a global callback handler from an individual callback handler just by glancing through your VBA. This is yet another benefit of planning ahead when creating callbacks. The reason why you can use global callbacks is because some instructions may overlap; and even when the instructions do not overlap, you can still benefit from the fact that certain controls share the same attribute for a specific action — such as the onAction attribute. In addition, if the actions are the same, then the procedures can be grouped together in a single callback handler. You can take advantage of this to reduce the number of callbacks to be handled, and instead of handling individual callbacks, you can handle the control itself through a common callback. Take, for example, the following objects: tab, group, and button. Each one of these controls shares a common attribute — namely, getLabel. Therefore, building on our previous statements, this tells you that you do not need to write a getLabel callback for each control in your XML code when you need to dynamically apply a value to each control. Instead, you can bring them together in a single process by sharing the task in a VBA procedure. Moreover, because it’s a callback, you do not need to loop through the controls specified in the XML. It will automatically go through the controls until they all have their required values. We can use the example from the previous section so that you can compare both methods and decide what is best for a given situation. All you need to do is change the onAction attribute for each of the buttons in the previous XML code to the following: onAction=”rxshared_click”
This will generate a single callback that can be used to handle any control that has an onAction attribute and shares this signature. The next step is to handle this callback for each button in VBA. That is accomplished with one small code snippet, as follows: Sub rxshared_click(control As IRibbonControl) MsgBox “You clicked on “ & control.Tag, vbInformation End Sub
Obviously, the preceding code is not really “handling” anything just yet, but it illustrates that there is no need to repeat the message box line inside each individual callback. It also gives you a taste of how handling various procedures through a single callback handler can save you a lot of time and hassle. As you would probably want to add individual code to each button, you can use a Select Case statement to structurally separate each button and assign it a specific piece of code: Sub rxshared_click(control As IRibbonControl) Select Case control.ID
91118c05.qxd:WileyRedTight
158
Part I
■
11/28/07
9:15 PM
Page 158
The Building Blocks for a Successful Customization
Case “rxbtnPaste” Your code goes here for the rxbtnPaste button Case “rxbtnCopy” ‘ Your code goes here for the rxbtnCopy button Case “rxbtnCut” ‘ Your code goes here for the rxbtnCut button End Select End Sub ‘
As you can see, there is no need to handle a callback for each custom control in the UI. Instead, you can use a global (shared) callback and then handle each control within the VBA procedure. We used a Select Case statement because it is concise and easy to interpret. You could also use an If … Then … ElseIf… End If statement.
Handling Callbacks in Access When it comes to customization, Access is unique in many ways. As you have already seen with something as basic as attaching the XML code to your project’s UI, Access uses a table, rather than a zipped group of XML files as in Excel and Word.
C R O S S-R E F E R E N C E See Chapter 16 for other methods of deploying a custom UI in Access.
In Access, you can use VBA or macros to handle your callbacks. When working with Word and Excel, the word “macro” typically refers to a VBA procedure, but in Access a macro is an object that you create. In addition, because macros have predefined instructions that typically only need an argument, there is little need to know much about programming in VBA. The next two sections describe how to use VBA and macros in Access to add functionality to your UI.
Using VBA to Handle Callbacks The obvious way to handle callbacks in Access is to use the same method that we used for Excel and Word. In Access, the biggest difference is that you need to reference the Office 12 Object Library. If you don’t reference this library, you will get the error message shown in Figure 5-5.
Figure 5-5: Error due to uninstalled Office 12 Object Library
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 159
Callbacks: The Key to Adding Functionality to Your Custom UI 159
C R O S S-R E F E R E N C E See Chapter 4 for instructions on how to reference libraries in your project.
The problem with the message is that it can be misleading. As you can see in Figure 5-5, the message doesn’t indicate the true root of what we know to be the problem. Although it may be true that a callback subprocedure or function is missing, it can also indicate that Access is unable to identify an existing object because the library itself is missing from the project. Either scenario will generate the same message. Therefore, considering that forewarned is forearmed, let’s get started with a simple example. The following XML code creates a single button for your custom tab/group in Access:
Now add a standard module to your project, to which the following code should be added for testing purposes: Sub rxbtnReport_Click(control As IRibbonControl) On Error GoTo Err_Handler DoCmd.OpenReport “MyReport”, acViewPreview Exit Sub Err_Handler: MsgBox Err.Description, vbCritical, “Err number: “ & Err.Number End Sub
91118c05.qxd:WileyRedTight
160
Part I
■
11/28/07
9:15 PM
Page 160
The Building Blocks for a Successful Customization
In this example, after the button is clicked it will open the report named MyReport in View Preview mode. However, if it encounters an error, instead of the report, it will display a message box with the error description and number.
Using Macros to Handle Callbacks With Access 12, the macro object is an alternative to using standard VBA code to handle your callbacks. If you plan to use macros to handle your callbacks in Access, you need to know that they are slightly different from VBA, and they need to be handled in XML and in Access. Suppose you want to handle an onAction attribute using a macro. You would need to specify the onAction attribute as follows: onAction=”rxMacroName.ObjectName”
Notice that, similar to the Excel example that runs a procedure in a different workbook, this procedure also consists of two parts: ■■
rxMacroName refers to the name of the macro object to be created in Access.
■■
ObjectName refers to the object id that was created in the XML code; which, in
turn, refers to a macro name. Do not mistake the macro object, which has a name, with the macro name itself, which is merely the identifier for the macro object. If that last bulleted point sounds confusing, Figure 5-6 should help it make more sense. It shows the macro object named rxsharedMacro, which contains three named macros: rxbtnPaste, rxbtnCopy, and rxbtnCut. Of course, we’re about to explain how to name and refer to macros.
Figure 5-6: Access’s macro window
Note that the tab of the macro window contains the macro object name, whereas the field Macro Name refers to the Ribbon object id (the object in your XML code). The reason for choosing the syntax rxMacroName.ObjectName is that the first part refers to the macro object itself; the second part refers to a macro name within the macro object; and that, in turn, points to an object in your XML code. By naming the macros in this manner, you should be able to identify the object in your XML code and match it to the macro object by looking up its name in the Macro Name field (column), as shown in Figure 5-6.
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 161
Callbacks: The Key to Adding Functionality to Your Custom UI 161
N OT E If the Macro Name column is not visible, just click the Macro Names toggleButton to display it. That is the button with the XYZ shown in Figure 5-6. To see a demonstration of how to use a macro for the callbacks, create a new tab and group and add the following buttons to it:
Now add a new macro object in Access and name it rxsharedMacro. After doing so, open the macro in Design View and add three new macro instructions, each one named using the text after the dot in the onAction attribute in your XML code. You’re now set to go. Your macro should look similar to the one shown in Figure 5-6. For demonstration purposes, we added three simple macro instructions: a beep and two messages. Although you wouldn’t find these in a functional application, their simplicity makes them effective learning tools. As shown in Figure 5-7, the message box generated by the third macro displays the comment in the macro’s argument. This demonstrates that the parameters set in the action’s argument grid (as shown in Figure 5-6) will display in the message box.
Figure 5-7: Message box generated by the macro rxbtnCut
91118c05.qxd:WileyRedTight
162
Part I
■
11/28/07
9:15 PM
Page 162
The Building Blocks for a Successful Customization
Although the messages relate to the buttons, in a real application the macro name would have more relevance to the action involved. For example, in a case like the one shown in this example, in a real application the macro name would likely be something like rxbtnMsgCut instead of rxbtnCut. We wanted to use a simple example to make it easier to grasp the principles. Once you understand the process, you can expand the actions by either writing a more complex macro instruction or by pointing the macro to a VBA function that runs more complex instructions.
Invalidating UI Components One important aspect of the Ribbon relates to invalidating the Ribbon or specific controls. As you will see, some actions can only be carried out if you invalidate the entire Ribbon, but other actions can be accomplished by just invalidating a specific control. This section explains how to invalidate the Ribbon, how to invalidate specific controls, and how to harness the invalidation power to your benefit.
What Invalidating Does and Why You Need It Before moving on to invalidation of the Ribbon and its controls, you need to understand what it actually means to you and your project. The IRibbonUI object contains two methods: Invalidate and InvalidateControl. These are described in Table 5-3. Table 5-3: IRibbonUI Object Methods METHOD
WHAT IT DOES
Invalidate()
Marks the entire Ribbon (consequently marking every control in it) for updating
InvalidateControl(strControlID)
Marks a specific control for updating. The control to be updated is passed as a string in the argument of the method. (strControlID)
A key to understanding the Invalidate method is to remember that it invalidates every control in the UI — meaning that it will force a call on all callbacks defined in the UI. In addition, it will cause a refresh on all controls whether they have a callback or not. That means that when the Ribbon is invalidated, a call is made on the Ribbon, and every procedure specified in the UI will run. This is likely to place a performance stress on your code, and it can slow things down considerably. Think of the Invalidate method as a way to refresh the controls in your UI, but do not confuse refreshing the controls in the UI with reloading the UI. In other words, refresh simply refreshes the controls already loaded in the UI when you opened the project, whereas reloading implies unloading and reloading the entire project along with the UI.
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 163
Callbacks: The Key to Adding Functionality to Your Custom UI 163
Unless you truly need to affect the entire Ribbon, a better option is to invalidate individual controls at specific moments during execution. That way, you only invalidate controls when necessary. In order to invalidate either the entire Ribbon or specific controls in the Ribbon, you need to set a global variable representing the IRibbonUI object. This is done by specifying a callback on the onLoad attribute, as previously shown. For your convenience, the XML syntax is repeated here:
Next, you need to write a piece of VBA code to handle the callback specified in the onLoad attribute: Public grxIRibbonUI As IRibbonUI Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set grxIRibbonUI = ribbon End Sub
Notice that the variable representing the IRibbonUI object is declared in the Global Declarations area of a standard module. That is because the object needs to be accessible to other parts of the project. Now that you’ve seen how to create a global variable to represent the entire Ribbon object, we’ll look at an example showing how to invalidate the entire Ribbon, and then we’ll discuss how to invalidate a specific control.
Invalidating the Entire Ribbon Now that you know how the invalidation process works, and you’re acquainted with the two methods available in the IRibbonUI object, you are ready to build a simple example. This first example demonstrates how invalidating the entire Ribbon will affect the controls in it.
N OT E The example discussed here is for Excel, but the chapter download also includes a file for Access and a file for Word that will replicate the example for those programs. First, create a new Excel file and save it as a macro-enabled workbook. Next, using the CustomUI Editor, attach the following XML code to the file:
After pasting in the code, save the file. With the CustomUI Editor still open, use the following steps to add the callbacks: 1. Click the Generate Callback button to generate the callbacks. 2. Copy the callbacks to the clipboard and close the workbook. 3. Open the workbook in Excel and add a standard module (don’t worry about the error message that appears). 4. With the standard module open, enter the following code (this is a great time to copy and paste from the online file): Public grxIRibbonUI As IRibbonUI Public glngCount1 As Long Public glngCount2 As Long Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set grxIRibbonUI = ribbon End Sub Sub rxshared_Click(control As IRibbonControl) grxIRibbonUI.Invalidate End Sub Sub rxshared_getLabel(control As IRibbonControl, ByRef returnedVal) Select Case control.ID Case “rxbtn” returnedVal = _
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 165
Callbacks: The Key to Adding Functionality to Your Custom UI 165
“Ribbon invalidated: “ & glngCount1 & “ times.” glngCount1 = glngCount1 + 1 Case “rxbtn2” returnedVal = _ “Ribbon invalidated: “ & glngCount2 & “ times.” glngCount2 = glngCount2 + 1 End Select End Sub
5. Finally, in order for the code to take effect, you need to save the workbook, close it, and then open it again. You’ll appreciate that the count displayed on the button has been added merely to illustrate our point. As you work through the next example, you’ll gain a better understanding of the rest of the code. Now click one of the new buttons that appears in the custom tab. The result will look like Figure 5-8. Note that as you click either button, both labels are updated. This is because both controls are invalidated when either button is clicked because the code invalidates the entire Ribbon. You’ll also see that the shared getLabel procedure is called once for the first button and again for the second button.
Figure 5-8: Invalidating the IRibbonUI object causes all controls in it to be invalidated.
The next example repeats this exercise but only invalidates the button clicked.
C AU T I O N There is a slight MS bug in here. If you click too fast on one control they can become out of sync. This does not seem to apply to Word, but both Excel and Access have demonstrated this slightly erratic behavior.
Invalidating Individual Controls A more realistic scenario would not require the entire IRibbonUI object to be invalidated. Instead, it would more likely need to invalidate a specific control. We demonstrate that now using exactly the same XML code as the previous example; however, instead of invalidating the entire IRibbonUI object, we invalidate only the button that triggered the click event. We will thereby be able to create a count indicating how many times the specific control was invalidated.
91118c05.qxd:WileyRedTight
166
Part I
■
11/28/07
9:15 PM
Page 166
The Building Blocks for a Successful Customization
As before, you need to declare the variables. You’ll notice that the onLoad event does not change. However, you need to modify the shared click and the shared getLabel events to read as follows: Public grxIRibbonUI As IRibbonUI Public glngCount1 As Long Public glngCount2 As Long Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set grxIRibbonUI = ribbon End Sub Sub rxshared_Click(control As IRibbonControl) Select Case control.ID Case “rxbtn” glngCount1 = glngCount1 + 1 Case “rxbtn2” glngCount2 = glngCount2 + 1 End Select grxIRibbonUI.InvalidateControl (control.ID) End Sub Sub rxshared_getLabel(control As IRibbonControl, ByRef returnedVal) Select Case control.ID Case “rxbtn” returnedVal = “I was invalidated “ & glngCount1 & “ times.” Case “rxbtn2” returnedVal = “I was invalidated “ & glngCount2 & “ times.” End Select End Sub
You can now click on an individual control and the count will increment only on the control that was invalidated by the click, as illustrated in Figure 5-9.
Figure 5-9: Have more control over the UI by invalidating control objects only.
Unlike invalidating the entire Ribbon, when you click the buttons on the UI, the button you click is the one that is invalidated; and therefore its click count is increased by one. Although you won’t notice a performance change with this few controls, you can easily understand the concept.
91118c05.qxd:WileyRedTight
11/28/07
Chapter 5
■
9:15 PM
Page 167
Callbacks: The Key to Adding Functionality to Your Custom UI 167
Conclusion This chapter discussed a key aspect of customizing the Ribbon: adding functionality through callbacks. You learned how to capture and use the Ribbon object, and how to organize your procedures through either individual or shared callbacks. Although writing callbacks from scratch can be a very complex process, you saw how to do it in case you choose that option. However, given the advantages of using the Custom UI Editor to generate the callbacks, we will rely on that for the remainder of the book. Another complexity that we covered involved scenarios in which a callback name is associated with different functions. This was demonstrated in a single Excel file and then extended to demonstrate how it can affect multiple projects or files. Because Access has some unique ways of handling customizations, this chapter also included sections devoted to Access. These showed you how to use macro objects in Access to handle callbacks, and how to amend the XML code accordingly. Finally, we described how to invalidate the entire Ribbon, and discussed some of the relevant implications. Based on our examples, we hope you agree that in most situations a better approach is to invalidate individual controls as needed. Now it is time to turn our attention to some basic Ribbon controls. Some of these have already cropped up in examples in this chapter, but they are explained in more detail in Chapter 6.
91118c05.qxd:WileyRedTight
11/28/07
9:15 PM
Page 168
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 169
CHAPTER
6 RibbonX Basic Controls
It’s time to start exploring the controls that reflect the RibbonX experience. This chapter begins that process by examining four of the most basic, yet most frequently used, Ribbon controls: button, checkBox, editBox, and toggleButton. This chapter is divided into four main sections, each of which discusses an individual control in great detail. Because the XML and VBA code required to make these controls function is supposed to be agnostic across the several Office applications, in theory they can all be covered once. However, we all know the benefit of seeing how something works in our primary application, so each section also includes examples that are specific to Excel, Word, and Access. When working through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s website at www.wiley.com/go/ribbonx.
The button Element Without question, the button is the most well known of all the tools in the developer’s toolbox. From early versions of the applications, users have had access to buttons on toolbars, menus, and even in other forms such as ActiveX controls. Some have pictures and others have text, and they can vary in size and shape, but they all have one thing in common: When you click a button, something happens. The Ribbon gives you control over a very rich button experience. You can use builtin images or supply your own; you can specify that you’d like a large button, or use its smaller form. With very little extra code you can even supply a label to go with it. In
169
91118c06.qxd:WileyRedTight
170
Part 1
■
11/30/07
5:25 PM
Page 170
The Building Blocks for a Successful Customization
addition, you have access to a wide variety of callback procedures that can be leveraged to make the button experience quite dynamic. The button offers several attributes — or elements — that are used to customize the look and response. Some attributes are required, others are optional, and a few allow you to pick one from a short list of candidates. The following tables will be a good reference when you are creating buttons; but for now, the acronyms, lists, and terms may seem a little overwhelming to those of you who are relatively new to XML, VBA, and programming. Keep in mind that each section and chapter in this book contains examples with detailed steps to walk you through the processes. In addition, you can always refer to Chapter 3 for a refresher on working with XML, or Chapter 4 to review VBA fundamentals. Namespaces, which you’ll see mentioned in the tables throughout this chapter, are explained in Chapter 16, in the section “Creating a Shared Namespace.”
Required Attributes of the button Element The button requires any one of the id attributes shown in Table 6-1. Table 6-1: Required Attributes of the button Element ATTRIBUTE
WHEN TO USE
id
When creating your own button
idMso
When using an existing Microsoft button
idQ
When creating a button shared between namespaces
Each button also requires the onAction callback from Table 6-2. Table 6-2: Required Callback of the button Element DYNAMIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE
onAction
1 to 4096 characters
Sub OnAction (control As IRibbonControl)
onAction
Repurposing
Sub OnAction (control As IRibbonControl, byRef CancelDefaultcancelDefault)
C R O S S-R E F E R E N C E The second version of the onAction callback is used when “repurposing” a built-in control. You’ll learn more about repurposing in Chapter 13.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 171
Chapter 6
■
RibbonX Basic Controls
Optional Static and Dynamic Attributes with Callback Signatures With a button, you have the option to use any one insert attribute from Table 6-3. Table 6-3: Optional insert Attributes of the button Element INSERT ATTRIBUTE ALLOWED VALUES DEFAULT VALUE WHEN TO USE insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control
insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
You may also provide any or all of the attributes from Table 6-4. Table 6-4: Optional Attributes and Callbacks of the button Element STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
description
getDescription 1 to 4096 characters
enabled
getEnabled
image
getImage
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
(none)
Sub GetDescription (control As IRibbonControl, ByRef returnedVal)
true, false, 1, 0
true
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
1 to 1024 characters
(none)
Sub GetImage (control As IRibbonControl, ByRef returnedVal) Continued
171
91118c06.qxd:WileyRedTight
172
Part 1
■
11/30/07
5:25 PM
Page 172
The Building Blocks for a Successful Customization
Table 6-4 (continued) STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
imageMso
getImage
1 to 1024 characters
(none)
(Same as above)
keytip
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 1024 characters
(none)
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
screentip
getScreentip
1 to 1024 characters
(none)
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal)
showImage
getShowImage true, false, 1, 0
true
Sub GetShowImage (control As IRibbonControl, ByRef returnedVal)
showLabel
getShowLabel true, false, 1, 0
true
Sub GetShowLabel (control As IRibbonControl, ByRef returnedVal)
size
getSize
normal, large
normal
Sub GetSize (control As IRibbonControl, ByRef returnedVal)
supertip
getSupertip
1 to 1024 characters
(none)
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 1024 characters
(none)
(none)
visible
getVisible
true, false, 1, 0
true
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 173
Chapter 6
■
RibbonX Basic Controls
Allowed Children Objects of the button Element The button control does not support child objects of any kind. We mention that to save you some of the time and frustration of looking for something that isn’t there. If you are accustomed to VBA programming, you might anticipate the ability to leverage children objects.
Parent Objects of the button Element The button control can be placed within any of the following controls: ■■
box
■■
buttonGroup
■■
dialogBoxLauncher
■■
documentControls
■■
dynamicMenu
■■
gallery
■■
group
■■
menu
■■
splitButton
■■
officeMenu
Graphical View of button Attributes Figure 6-1 shows a sample customization that displays all of the visible graphical attributes that you can set on the button control.
Figure 6-1: Graphical view of button attributes
173
91118c06.qxd:WileyRedTight
174
Part 1
■
11/30/07
5:25 PM
Page 174
The Building Blocks for a Successful Customization
Using Built-in button Controls Adding built-in controls to a group is actually fairly simple. Let’s say that you are working in Excel and would like that nice, pretty little “$” from the Accounting Number Format control sitting in your own custom group. Intuitively, you’d expect it to be as easy as doing the following: 1. Create a new .xlsx file and save it as Excel Built In Button Example.xlsx. 2. Close the file in Excel the open it in the CustomUI Editor. 3. Apply the RibbonBase template to the file. 4. Insert the following XML between the and tags:
Lo and behold, it doesn’t create what you were expecting. Figure 6-2 shows the group that will be created by the preceding code.
Figure 6-2: Built-in control’s default appearance
You didn’t ask for that text . . . or did you? Be aware that when you are working with built-in controls, you get the entire package: image, label, pre-programmed callbacks, and all. Therefore, if you want just the image and the functionality, you need to set the showLabel attribute to false in addition to requesting the desired idMso. To do that, you simply modify the code to read as follows:
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 175
Chapter 6
■
RibbonX Basic Controls
Now you will create a nice little button that shows only the image, and not the label, as shown in Figure 6-3. Of course, it still has all the other properties of the built-in control. In other words, clicking the control will change the number style, just as you’d expect.
Figure 6-3: Built-in controls with showLabel=“false”
A button Idiosyncrasy: The showLabel Attribute Now suppose you’ve decided that the little icon that you received when working with the prior example just won’t do, and you’ve gone back to make it larger. Based on the original example, you might think that you could just change the portion of the code that deals with the button to reflect the larger size:
The result of using the preceding code is shown in Figure 6-4. What’s going on here? The label is showing up even though the code explicitly dictates that it should not!
Figure 6-4: Built-in control’s default large appearance
As it turns out, this is a strange idiosyncrasy of the button model. While the approach will work as expected for a button that is of size=“normal”, it won’t work for a button that is specified as size=“large”. As you can see, when the button is size large, the default values override custom settings for attributes other than the size of the symbol and label text. Despite this, it is still possible to get the button to display in a large format without the label. The secret is to specify the label text as a blank space. This kind of coding is generally frowned upon in practice, as it is not very elegant, but you must supply at least one character for a label, so it is currently the only way to accomplish our goal.
175
91118c06.qxd:WileyRedTight
176
Part 1
■
11/30/07
5:25 PM
Page 176
The Building Blocks for a Successful Customization
The XML would therefore look like the following:
As shown in Figure 6-5, this achieves the desired label-free button with a large image.
Figure 6-5: Built-in control with the desired large icon
Creating Custom button Controls While it’s great that you can add built-in buttons to your groups, that is only the tip of the iceberg of what you want and truly need to be able to do. You’re hungry for a place to store a custom macro that you built, and you don’t want it attached to a simple forms button or to an ActiveX control. You want to learn how to add your own button to the Ribbon, and have it fire your macro when it is clicked. It’s time to create some buttons that do something you might want to accomplish in the real world.
An Excel Example For this example, assume that you are an accountant, and you’ve built a continuity schedule for your prepaid expenses. It might look similar to the file shown in Figure 6-6, in which you’d expect the user to enter data in the shaded cells.
Figure 6-6: Example of a continuity schedule
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 177
Chapter 6
■
RibbonX Basic Controls
T I P This file is included in the Chapter 6 example files under the name Button–Monthly Roll Forward.xlsm.
Now that you have your continuity schedule built and saved as a macro-enabled file, it’s time to write the macro that will run on a monthly basis to prepare the sheet for the next month. Specifically, the macro needs to do the following: ■■
Copy the ending balance to the opening balance column.
■■
Clear the Additions and Usage area (for this month’s entries).
■■
Advance the date to the next month’s end.
■■
Advise the user to save the file.
To do that, you need to write some VBA like the following, and save it in a standard module: Public Sub RollForward() With ActiveSheet .Range(“E6:E10”).Copy .Range(“B6:B10”).PasteSpecial Paste:=xlValues .Range(“C6:D10”).ClearContents .Range(“A3”) = .Range(“A3”).Value + 40 _ - DatePart(“d”, .Range(“A3”).Value + 40) End With MsgBox “Rolled forward successfully!” & vbCrLf & _ “Please save the file under a new name.”, _ vbOKOnly + vbInformation, “Success!” End Sub
If you’d like, you can test this macro by pressing Alt+F8 and choosing RollForward from the box. By putting data in the C6:D10 range, you’ll notice that the macro does each of the tasks on the list. Well, this is all great and wonderful, but you want to attach it to a button, rather than press the keyboard shortcut each time. To that end, save and close the Excel file and open it again with the CustomUI Editor. From there, apply the RibbonBase template to the file and insert the following code between the and tags:
177
91118c06.qxd:WileyRedTight
178
Part 1
■
11/30/07
5:25 PM
Page 178
The Building Blocks for a Successful Customization
C R O S S-R E F E R E N C E If you don’t remember how to set up the RibbonBase template, review “Storing Customization Templates in the CustomUI Editor” from Chapter 2.
After you’ve validated the code to make sure your XML is well formed, click the Generate Callbacks button and copy the code for your onAction callback. Once you’ve taken care of that little detail, save the file, close the CustomUI Editor, and reopen the file in Excel. Do not click the button yet, however, as it won’t do anything except throw the error message shown in Figure 6-7.
N OT E Remember to enable macros when running this type of file. Although using built-in controls does not require that the file be code-enabled, any file that requires VBA code needs to have macros enabled.
Figure 6-7: Error indicating a missing callback
Obviously, in this case, you would expect this error because the callback hasn’t been programmed, so it is time to take care of that little issue. Go back into the Visual Basic Editor, enter the module in which you saved your RollForward subroutine, and paste the callback code that you copied from the CustomUI Editor. You’ll then add a tiny bit of code to your callback so that it calls the existing procedure that you already wrote and tested. Your callback will then look like this: ‘Callback for rxbtnRollForward onAction Sub rxbtnRollForward_Click(control As IRibbonControl) Select Case control.ID Case Is = “rxbtnRollForward” Call RollForward Case Else ‘do nothing End Select End Sub
That’s all there is to it. Just exit the Visual Basic Editor, save your file, and start clicking your button to watch your code be called.
WA R N I N G If you did click the button prematurely and receive the error shown in Figure 6-7, you will need to save, close, and reopen the file in order for the code to take effect. This is because any UI errors break the hooks to the Ribbon and the file must be re-accessed in order for the hooks to be restored.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 179
Chapter 6
■
RibbonX Basic Controls
A Word Example In this example you add the capability to update all fields in the document at once. This is a nice automation to add because the only way to do this natively is to print the document. You’ll start by opening your favorite Word document and saving it as a .docm file. Open the Visual Basic Editor and add the following code in a new standard module: Public Sub UpdateDocumentFields() Dim rngStory As Word.Range For Each rngStory In ActiveDocument.StoryRanges rngStory.Fields.Update Do If rngStory.NextStoryRange Is Nothing Then Exit Do Set rngStory = rngStory.NextStoryRange rngStory.Fields.Update Loop Next rngStory End Sub
If you’d like to test this, just create a document with some calculated fields. (Alternately, you could just load the example file called button-Update Word Fields.docm.) You could use a formula in a table, cross-references to another section of the document, or whatever calculated field that you like. For the purposes of building this example, the document shown in Figure 6-8 was constructed using the following field codes: Reference { STYLEREF 1 \s }-{ SEQ Figure \* ARABIC \s 1}
Figure 6-8: Example of non-updated Word fields
179
91118c06.qxd:WileyRedTight
180
Part 1
■
11/30/07
5:25 PM
Page 180
The Building Blocks for a Successful Customization
After you have your choice of fields set up correctly (or you are in the example file), just copy and paste the line a few times. You’ll notice that the numbers do not update for you. Run the preceding macro and you’ll notice that all the fields are updated to new index numbers. To return the document’s appearance to its original state, just copy the first reference line and paste it over all of the existing ones again. Now that you have something to work with, save the file, close it, and open it again in the CustomUI Editor. Apply the RibbonBase template to the file, and insert the following XML between the and elements:
Don’t forget to validate the code before you save it, just to catch any of those pesky little typing errors in XML-specific code. After you’ve done that, click the Generate Callbacks button and copy the code provided. Close the CustomUI Editor, reopen the document in Word, and head straight into the VBE. Paste the callback code in the same module in which you stored the UpdateDocumentFields routine, and modify calling that routine as shown here: ‘Callback for rxbtnUpdateFields onAction Sub rxbtnUpdateFields_click(control As IRibbonControl) Call UpdateDocumentFields End Sub
Close the VBE, save your file, and now turn your attention to the button that appears on the Demo group (right before the Home tab), as shown in Figure 6-9. Go ahead, give it a click. Your fields should all update automatically for you.
Figure 6-9: Update Fields button
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 181
Chapter 6
■
RibbonX Basic Controls
An Access Example This section describes how a button is used in Access. Assume that you’re building an application and you want to provide your users with the capability to launch a form from a Ribbon group. It’s your lucky day, as that’s exactly what this example will do! To start, create a database and set up the RibbonUI so that it is completely linked to the project.
C R O S S-R E F E R E N C E If you need to work from a guide, follow the instructions in Chapter 2 to set up a new RibbonUI in a database. You’ll replace the XML later, but for now it’s important to get the UI linked to work with.
Once you have that done, you need to create a little structure to work with. From the Create tab, create a new table. You’ll notice that the Datasheet tab on the Ribbon is immediately activated. Click the View drop-down, choose Design View, and save the table as tblAuthors when prompted. Set up your table as shown in Figure 6-10.
Figure 6-10: tblAuthors table design
Close the table, saving it when prompted, and reopen it. Populate the table with the information shown in Figure 6-11.
Figure 6-11: tblAuthors data
Close the table again and ensure that it is selected in the navigation window. Again, on the Create tab, click Form. Delete the ID field and its corresponding entry field, select the name, and change it to read “Author Information.” Your form should now look like the one displayed in Figure 6-12.
181
91118c06.qxd:WileyRedTight
182
Part 1
■
11/30/07
5:25 PM
Page 182
The Building Blocks for a Successful Customization
Figure 6-12: frmAuthors display
Close the form, saving it as frmAuthors, and breathe a sigh of relief. The database structure has been completed, and you’re ready to link it all to the Ribbon. At this point, you need to create the XML code to display your Ribbon commands. Depending on how much you need to do, and how familiar you are with the tools, you may elect to do this using either the CustomUI Editor or XML Notepad. For the purposes of this example, however, you’ll stick with the CustomUI Editor for the moment. Open the CustomUI Editor and immediately apply the RibbonBase template discussed in Chapter 2. Replace the line that reads with the following XML code:
You’ll recall that one of the reasons for writing code in the CustomUI Editor is to confirm that the code is valid, so make sure that you validate the code, and then copy everything in the window (not just what you just entered). Don’t close the CustomUI editor just yet, but head back to Access and open the USysRibbons table. Paste the code in the RibbonXML field for the MainRibbonUI and save it. Now, close your database, reopen it, and take a moment to admire your new tab, shown in Figure 6-13.
Figure 6-13: The Enter Authors button on the My Tools tab
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 183
Chapter 6
■
RibbonX Basic Controls
Unfortunately, as pretty as this looks, it doesn’t do anything yet, as it has not been linked to any code. Ultimately, however, you’d like to have the user click the button and launch your form. To accomplish this, you need to write a little VBA code. The first step, of course, is to create a new VBA module to hold the VBA code. Therefore, on the Create tab, click the Macro drop-down (the last command on the right), and choose Module. This will open a new window with the VBE (Visual Basic Editor), and you’ll be staring at a fresh, almost blank page. Head back into the CustomUI Editor and click the Generate Callbacks button. Copy the resulting code and close the CustomUI Editor. When you return to Access, go to the VBE and paste the code at the end of the module that was just created. Now, modify the code to read as follows: ‘Callback for rxbtnFrmAuthors onAction Public Sub rxbtnFrmAuthors_click(control As IRibbonControl) DoCmd.OpenForm “frmAuthors”, acNormal End Sub
The preceding code leverages the OpenForm method of the DoCmd object. Notice that you’ve fed it the name of the Authors form, and you’ve specified that you would like the form opened in Normal View.
T I P Don’t forget to set a reference to the MS Office 12.0 Objects Library, as described in Chapter 5. Finally, save your module as modRibbonX and close it. Try clicking the button. Your form should jump open in front of you!
WA R N I N G If you already clicked the button and received an error, you will need to close and reopen your database to reactive the Ribbon interface again. As noted with the Excel example, this is because any UI errors break the hooks to the Ribbon; to restore the hooks you need to re-access the file.
The checkBox Element The checkBox control enables users to toggle between two states. Although these states are true and false by default, this element could indicate on/off, up/down, left/right, 1/0, or any other combination of opposite states that the developer could imagine. There are numerous scenarios in which you might wish to use a checkBox to control or indicate something. A couple of practical examples might help prompt some ideas of your own. For example, you might benefit from using a checkBox for the following: ■■
To indicate whether a specific criterion has been met in a field in your database. In this case, when the criteria is met, the check would automatically appear in the box.
183
91118c06.qxd:WileyRedTight
184
Part 1 ■■
■
11/30/07
5:25 PM
Page 184
The Building Blocks for a Successful Customization
To allow the user to determine whether an object should be displayed or not, such as to show or hide gridlines or even a subform.
As you explore the examples in this section, you’ll begin to see how the concept of invalidation is applied in a real-world setting as well.
Required Attributes of the checkBox Element The checkBox control requires any one of the id attributes shown in Table 6-5. Table 6-5: Required Attributes of the checkBox Element ATTRIBUTE
WHEN TO USE
id
Create your own checkBox
idMso
Use an existing Microsoft checkBox
idQ
Create a checkBox shared between namespaces
The checkBox control also requires the onAction callback, shown in Table 6-6. Table 6-6: Required Callback for the checkBox Element DYNAMIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE
onAction
1 to 4096 characters
Sub OnAction (control As IRibbonControl, pressed as Boolean)
Optional Static and Dynamic Attributes with Callback Signatures In addition, the checkBox control can optionally make use of any one insert attribute, shown in Table 6-7. Table 6-7: Optional insert Attributes of the checkBox Element INSERT ATTRIBUTE ALLOWED VALUES DEFAULT VALUE WHEN TO USE insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 185
Chapter 6
■
RibbonX Basic Controls
Table 6-7 (continued) INSERT ATTRIBUTE ALLOWED VALUES DEFAULT VALUE WHEN TO USE insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control
insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
The checkBox element may also employ any or all of the attributes shown in Table 6-8. Table 6-8: Optional Attributes and Callbacks of the checkBox Element STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
description
getDescription 1 to 4096 characters
enabled
getEnabled
keytip
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
(none)
Sub GetDescription (control As IRibbonControl, ByRef returnedVal)
true, false, 1, 0
true
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 1024 characters
(none)
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
(none)
getPressed
true, false, 1, 0
false
Sub GetPressed (control As IRibbonControl, ByRef returnedVal)
screentip
getScreentip
1 to 1024 characters
(none)
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal) Continued
185
91118c06.qxd:WileyRedTight
186
Part 1
■
11/30/07
5:25 PM
Page 186
The Building Blocks for a Successful Customization
Table 6-8 (continued) VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
supertip
getSupertip
1 to 1024 characters
(none)
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 1024 characters
(none)
(none)
visible
getVisible
true, false, 1, 0
true
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
Allowed Children Objects of the checkBox Element The checkBox control does not support child objects of any kind.
Parent Objects of the button Element The checkBox control can be placed within any of the following controls: ■■
box
■■
dynamicMenu
■■
group
■■
menu
■■
officeMenu (Note that when the checkbox is unchecked, only the description,
but not the checkbox, appears on the menu.)
Graphical View of checkBox Attributes Figure 6-14 shows a sample customization that displays all of the visible graphical attributes that you can set on the checkBox control.
Figure 6-14: Graphical view of checkBox attributes
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 187
Chapter 6
■
RibbonX Basic Controls
Using Built-in checkBox Controls To demonstrate the use of a built-in control, you’ll add a custom tab in Word, and put the View Gridlines checkBox on it. To do this, create a new Word document and save it as a .docx file.
N OT E Because you are only using built-in controls, and do not need to program any callback macros, the file does not have to be saved in a macroenabled format. This is an exception to the norm, because most Word and Excel files with custom Ribbons will include macros and therefore require a file extension ending with “m”.
Close the document in Word and open it in the CustomUI Editor. Apply the RibbonBase template to the file, and then insert the following XML between the and tags:
Validate the code to ensure that you’ve typed it correctly, and then save and close the file in the CustomUI Editor. Reopen the document in Word and click the Demo tab to the left of the Home tab, as shown in Figure 6-15.
Figure 6-15: Built-in checkBox default appearance
You’ll notice that the checkBox is there and that it works, although the name is probably not what you’re after. Why not go back, edit the XML, and give it a new name. Update the XML to add a label to the code that declares the checkBox. Instead of one line, you will have two lines, as shown here:
Upon reopening your document, your group should now look like what is shown in Figure 6-16.
187
91118c06.qxd:WileyRedTight
188
Part 1
■
11/30/07
5:25 PM
Page 188
The Building Blocks for a Successful Customization
Figure 6-16: Built-in checkBox relabeled
N OT E The preceding example will work in Excel with only one minor change: Instead of using idMso=”ViewGridlinesWord” in your XML, substitute idMso=”GridlinesExcel” and save it in an Excel file.
Creating Custom Controls Now it’s time to explore some of the diversity of the Ribbon checkBox and create your own customizations. Again, you’ll have the opportunity to work through an example in Excel, Word, and Access. So let’s get started!
An Excel Example One of the things that can be handy when working in Excel is the capability to quickly toggle between A1 and R1C1 formulas in your workbook. There are several scenarios in which it may be more advantageous to place your formulas via R1C1 notation, so this example demonstrates how to make it easy to quickly flip your formulas to display in the alternate notation.
N OT E For users who may not be familiar with the difference between A1 and R1C1 notation, A1 notation enables users to specify formulas and references by pointing to the cell’s coordinates in the spreadsheet grid. R1C1 notation, however, tends to be more like referring to an off-setting cell a certain number of rows and columns in any direction. Excel allows either notation type. This example adds a control to the Formulas tab to save you the hassle of having to dig through the Office menu to find the checkbox for that setting. The completed UI modification is shown in Figure 6-17, with the new checkBox control on the far right, in the Other Settings group.
Figure 6-17: R1C1 formula checkbox
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 189
Chapter 6
■
RibbonX Basic Controls
To begin, create a new workbook and insert all the required XML. There’s going to be a bit of VBA code involved to set this up, so make sure you save the workbook as an .xlsm workbook, and then close the file. Open the file with the CustomUI Editor, apply the RibbonBase template, and insert the following XML code between the and tags:
In addition, you need to modify the first line of the XML code to capture the Ribbon object for later use:
N OT E Remember that although it may seem wrong at first glance, the last two lines are and . This is because each tag must be individually closed. The code that you are inserting has a that partners with the opening line, and the entire tab is encompassed by the and . Again, don’t forget to validate it before you save the file, and be sure to copy the callback signatures before you close it. Reopen the file in Excel, open the VBE, and paste the code in a new standard module. You’ll notice that there are now three callback signatures in the file: rxIRibbonUI_onLoad, rxchkR1C1_getPressed, and rxchkR1C1_click. These have the following purposes: ■■
rxIRibbonUI_onLoad will store the RibbonUI object for you, enabling you to invalidate controls later to force their updates.
■■
rxchkR1C1_getPressed is fired when the Formulas tab is first activated (or upon invalidation) and sets the checkBox appropriately.
■■
rxchkR1C1_click is triggered whenever the checkbox is checked or unchecked.
The purpose of this macro is to actually toggle the setting from on to off. Before you can use this, you need to lay a little more groundwork. The rxIRibbonUI_onLoad needs a custom workbook property to operate, so that should be set up first. Browse to the ThisWorkbook module for the project, and enter the following code: ‘Private variables to hold state of Ribbon and Ribbon controls Private pRibbonUI As IRibbonUI
189
91118c06.qxd:WileyRedTight
190
Part 1
■
11/30/07
5:25 PM
Page 190
The Building Blocks for a Successful Customization
Public Property Let RibbonUI(iRib As IRibbonUI) ‘Set RibbonUI to property for later use Set pRibbonUI = iRib End Property Public Property Get RibbonUI() As IRibbonUI ‘Retrieve RibbonUI from property for use Set RibbonUI = pRibbonUI End Property
Once that has been accomplished, you’ll want to complete the rxIRibbonUI_onLoad routine. Browse back to the standard module and modify this routine to look as follows: Private Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) ‘Set the RibbonUI to a workbook property for later use ThisWorkbook.RibbonUI = ribbon End Sub
Your RibbonUI object should now be captured when the workbook is loaded, and will be available for invalidation.
N OT E The preceding code uses public properties but it could have used public variables instead. We chose to work with the properties in this case because we will be invalidating the code in several procedures, and using properties will be more elegant. The next step in the process is to generate the macro to toggle this setting. The easiest way to work out this bit of code is to record a macro, so start the macro recorder and create a new macro that will be stored in the workbook you just created.
C R O S S-R E F E R E N C E If you need a refresher on how to use the macro recorder, review “Recording Macros for Excel and Word” in Chapter 4.
Now, go to the Office Menu, choose Excel Options ➪ Formulas, and check (or uncheck) the R1C1 Reference Style checkbox. Once you have done this, stop the macro recorder and jump into the VBE to examine the code. It will most likely look like this: Application.ReferenceStyle = xlR1C1
This is a situation in which the macro recorder can really help you, as it tells you exactly what objects you need to reference. You still need to make some modifications to the code, but at least you know where to start and you have the correct syntax. Fill in the getPressed callback first. It’s actually not too difficult. Basically, you want to determine whether the application is in R1C1 mode, and if so return true. This can be accomplished by modifying the routine to read as follows: ‘Callback for rxchkR1C1 getPressed Sub rxchkR1C1_getPressed(control As IRibbonControl, ByRef returnedVal)
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 191
Chapter 6
■
RibbonX Basic Controls
If Application.ReferenceStyle = xlR1C1 Then returnedVal = True End Sub
Next, you want to deal with the callback that handles the actual clicking of the checkbox. With your checkbox, the pressed parameter tells you whether the checkbox is checked (pressed=true) or not (pressed=false). This means that you can test the pressed argument and react accordingly: ‘Callback for rxchkR1C1 onAction Sub rxchkR1C1_click(control As IRibbonControl, pressed As Boolean) Select Case pressed Case True Application.ReferenceStyle = xlR1C1 Case False Application.ReferenceStyle = xlA1 End Select End Sub
If you were to click the Formulas tab now, the getPressed routine would fire, setting the checkbox to indicate what display mode you’re currently in. Likewise, checking the box would set it to the opposite state. Unfortunately, there is an issue remaining with this setup: What if someone goes through the Excel Options to change the value of that checkBox? In that case, the checkBox will not be updated. While you can’t make this perfectly transparent, you can force it to update whenever you activate a new worksheet. (This will also force an update when your workbook is reactivated as well.) To do this, you make use of the capability to invalidate a specific control — in this case, the rxchkR1C1 checkBox. Head back into the ThisWorkbook code module, and choose Workbook from the drop-down list on the left, as shown in Figure 6-18.
Figure 6-18: Selecting the Workbook code container
You will be greeted by a new procedure in your code called Workbook_Open. Before you delete it, choose Sheet_Activate from the right hand drop-down list. Modify the resulting procedure to read as follows: Private Sub Workbook_SheetActivate(ByVal Sh As Object) ‘Invalidate the tab each time a worksheet is activated ThisWorkbook.RibbonUI.InvalidateControl (“rxchkR1C1”) End Sub
191
91118c06.qxd:WileyRedTight
192
Part 1
■
11/30/07
5:25 PM
Page 192
The Building Blocks for a Successful Customization
T I P You could have typed your code in instead of selecting the appropriate procedure from the drop-down lists, but going this route offers you the benefits of browsing the available events, and ensures that they are syntactically correct when you set them up. That’s it. Save and reopen your workbook, and then place the following formula in cell B1: =A1 When you click the checkBox, it will change to =RC[-1]. Notice that changing the R1C1 setting through the Excel Options screen does not immediately update your checkBox, but selecting a different sheet and coming back to the first sheet does.
A Word Example Word also has some useful tools that might seem buried, such as those in the Word Advanced Options window. For instance, it would be handy to have the capability to quickly show or hide the Style Inspector window so that you can see what styles are active in your document. This next example focuses on using a checkBox to control this setting on a custom Ribbon tab. Because this setting is effective in Draft or Outline views only, it probably makes most sense to put this information on the View tab. To do this, you again begin by creating a new Word file and inserting the following code in a new standard module: Private Sub ShowStyleInspector() ‘Show the Style inspector window ActiveWindow.StyleAreaWidth = InchesToPoints(1) If ActiveWindow.View.SplitSpecial = wdPaneNone Then ActiveWindow.ActivePane.View.Type = wdNormalView Else ActiveWindow.View.Type = wdNormalView End If End Sub Private Sub HideStyleInspector() ‘Hide the Style inspector window ActiveWindow.StyleAreaWidth = 0 End Sub
Because both of these routines have been marked Private, they are not included with the macros listed in the Macro window. To try them out, you need to enter the VBE (Alt+F11), click somewhere within the appropriate macro, and press F5 to run it. If you do decide to try the ShowStyleInspector macro, you’ll notice upon leaving the VBE that you are switched to Draft view, and the Style Inspector, displaying the “Normal” style, is shown on the left side of the screen, as illustrated in Figure 6-19.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 193
Chapter 6
■
RibbonX Basic Controls
Figure 6-19: The Style Inspector
Likewise, run the HideStyleInspector routine to hide the Style Inspector. The next step is to save the file as a macro-enabled document (.docm), exit Word, and open the file in the CustomUI Editor. Apply the RibbonBase template to the file, and insert the following XML between the and elements:
Again, validate the code before you save it, and click the Generate Callbacks button to copy the callback code. Close the CustomUI Editor and reopen the document in Word. Do not click the View tab, but rather head straight into the VBE again. (If you don’t, you will get an error and will need to reload the file once your callbacks have been successfully implemented.) Paste the generated callbacks code in the module that holds the two routines you saved earlier and modify them to read as follows: Sub rxchkStyleInsp_getPressed(control As IRibbonControl, ByRef returnedVal) ‘Callback for rxchkStyleInsp getPressed Select Case ActiveWindow.View.Type Case Is = wdNormalView, wdOutlineView If ActiveWindow.StyleAreaWidth > 0 Then returnedVal = True Else returnedVal = False End If Case Else returnedVal = False End Select End Sub
193
91118c06.qxd:WileyRedTight
194
Part 1
■
11/30/07
5:25 PM
Page 194
The Building Blocks for a Successful Customization
Sub rxchkStyleInsp_click(control As IRibbonControl, pressed As Boolean) ‘Callback for rxchkStyleInsp onAction Static lState As Long Select Case pressed Case True lState = ActiveWindow.View.Type Call ShowStyleInspector Case False Call HideStyleInspector ActiveWindow.View.Type = lState End Select End Sub
So what’s happening here? The getPressed routine will fire the first time you click the View tab, and will check the width of the StyleArea. If it’s greater than 0, the checkbox will be flagged as true, and display a checkmark. If not, it won’t be displayed. The rxchkStyleInsp_click routine is fired when you actually click the checkBox. It checks the state of the button; if it is true (checked), it calls the ShowStyleInspector macro. Otherwise, it calls the routine to hide the Style Inspector.
T I P Notice the Static
variable lState that was included in this routine.
This records the view state before calling the macro to show the Style Inspector. Because it is a static variable, it holds its values in scope between macro executions, and you can then use it to return the view to its original state upon calling the macro to turn off the Style Inspector!
Now close the VBE, save your file, and click the View tab. You will see a new group with your checkBox, as shown in Figure 6-20.
Figure 6-20: The Style Inspector checkbox
Give it a click and the Style Inspector will become visible. Uncheck it and it goes away. In addition, thanks to the Static variable, this even remembers the view setting that was active when you clicked the checkBox!
An Access Example One of the things that can be a little daunting to a new user, and that causes frustration for users who frequently set up customizations, is that the USysRibbons table is a system
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 195
Chapter 6
■
RibbonX Basic Controls
object, and therefore it is hidden by default. It can be quite the unpleasant surprise for novice users to have their new table disappear as soon as they save it. With that in mind, this next example creates a checkbox on the Ribbon to show/hide the Systems Objects. To make life easier, why not just add this to the project that you started working on button controls earlier? Find that file and let’s get started.
T I P You can always download the completed button example file from the website if you want to catch up, or even start fresh at this point. Open the previous example and open the USysRibbons table. Copy all of the code in the RibbonXML field, and then open a fresh instance of the CustomUI Editor and paste the code there. Now you will insert a new group, rxgrpTools. Put the cursor between the and lines, and insert the following XML code:
Again, validate your code and then copy the entire code listing. Leave the CustomUI Editor open, and switch back to Access. Replace the value of the RibbonXML field with what you just copied. Save and close the table. Switch back to the CustomUI Editor and press the Generate Callbacks button. You’ll notice that three callback signatures are listed. Because the example file already holds the callback signature for the rxbtnFrmAuthors button, you only need to worry about copying the rxchkShowSysObjects callbacks. After you have done so, you may close the CustomUI Editor. Flip back to Access, open the modRibbonX module, and paste your new callbacks at the end of the module. You’ll then need to modify them to actually do something when called, so revise them to read as follows: `Callback for rxchkShowSysObjects getPressed Sub rxchkShowSysObjects_getPressed(control As IRibbonControl, _ ByRef returnedVal) returnedVal = Application.GetOption(“Show System objects”) End Sub `Callback for rxchkShowSysObjects onAction Sub rxchkShowSysObjects_click(control As IRibbonControl, _ pressed As Boolean) Application.SetOption “Show System objects”, pressed End Sub
The preceding code uses the getPressed callback to check whether the Show System Objects setting is true when your tab is first activated. If it is true, then the checkBox
195
91118c06.qxd:WileyRedTight
196
Part 1
■
11/30/07
5:25 PM
Page 196
The Building Blocks for a Successful Customization
will appear as checked; if it is false, then the checkBox will be empty. The second routine actually toggles the setting. Because the object model requires a true/false value to be specified in order to set this option, the checkBox state can simply be queried and fed right to it! The final step in making this work is to save the code module and reload the database to make the Ribbon code effective. Upon doing so, you’ll notice that the new group has been added to the tab, as shown in Figure 6-21.
Figure 6-21: The Show System Objects checkBox set to false
Now give the checkBox a click. In addition to seeing all of the tables that Access creates by default, you’ll be happy to see that your USysRibbons table is prominently displayed as well.
N OT E This setting is a global setting and will affect all databases opened in your Access application. This checkBox is intended to make your life as a developer a little easier, but it is not something that you’d deploy to all of your users. If you use this technique, make sure that your deployment checklist includes a step to hide system tables.
The editBox Element The editBox control allows users to enter text. This is quite handy if you are looking for some kind of input from the user. You might use an editBox to rename a worksheet in Excel, or to assign a caption to each image in Word, for example. The basic concept is that the user is asked for the input, which is then used when the editBox loses focus, the event that occurs just before the next control in the sequence is activated. Although some developers may prefer to provide a second button for users to click that will run code to store the text from the edit box, we’ve found that using loss of focus works quite well for this purpose.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 197
Chapter 6
■
RibbonX Basic Controls
Required Attributes of the editBox Element The editBox requires any one of the id attributes shown in Table 6-9. Table 6-9: Required Attributes of the editBox Element ATTRIBUTE
WHEN TO USE
id
When creating your own editBox
idMso
When using an existing Microsoft editBox
idQ
When creating an editBox shared between namespaces
The editBox also requires the onChange callback shown in Table 6-10. Table 6-10: Required Callback for the editBox Element DYNAMIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE
onChange
1 to 4096 characters
Sub OnChange (control As IRibbonControl, text As String)
Optional Static and Dynamic Attributes with Callback Signatures In addition to the required attributes, your editBox can optionally make use of any one insert attribute, described in Table 6-11. Table 6-11: Optional insert Attributes of the editBox Element INSERT ATTRIBUTE ALLOWED VALUES DEFAULT VALUE WHEN TO USE insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control Continued
197
91118c06.qxd:WileyRedTight
198
Part 1
■
11/30/07
5:25 PM
Page 198
The Building Blocks for a Successful Customization
Table 6-11 (continued) INSERT ATTRIBUTE ALLOWED VALUES DEFAULT VALUE WHEN TO USE insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
The editBox can also employ any or all of the attributes described in Table 6-12. Table 6-12: Optional Attributes and Callbacks of the editBox Element VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
enabled
getEnabled
true, false, 1, 0
true
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
image
getImage
1 to 1024 characters
(none)
Sub GetImage (control As IRibbonControl, ByRef returnedVal)
imageMso
getImage
1 to 1024 characters
(none)
Same as above
keytip
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 1024 characters
(none)
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
maxLength
(none)
1 to 1024 characters
1024
(none)
screentip
getScreentip
1 to 1024 characters
(none)
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal)
showImage
getShowImage true, false, 1, 0
true
Sub GetShowImage (control As IRibbonControl, ByRef returnedVal)
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 199
Chapter 6
■
RibbonX Basic Controls
Table 6-12 (continued) ALLOWED VALUES
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
showLabel
getShowLabel true, false, 1, 0
true
Sub GetShowLabel (control As IRibbonControl, ByRef returnedVal)
sizeString
(none)
1 to 1024 characters
12*
(none)
supertip
getSupertip
1 to 1024 characters
(none)
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 1024 characters
(none)
(none)
(none)
getText
1 to 4096 characters
(none)
Sub GetText (control As IRibbonControl, ByRef returnedVal)
visible
getVisible
true, false, 1, 0
true
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
N OT E *The default value for the sizeString attribute (if the attribute is not declared) is approximately 12, but this will vary based on the characters used and the system font.
Allowed Children Objects of the editBox Element The editBox control does not support child objects of any kind.
Parent Objects of the editBox Element The editBox control can only be placed within the following two controls: ■■
box
■■
group
199
91118c06.qxd:WileyRedTight
200
Part 1
■
11/30/07
5:25 PM
Page 200
The Building Blocks for a Successful Customization
Graphical View of editBox Attributes Figures 6-22 and 6-23 display all of the visible graphical attributes that you can set on an editBox control.
Figure 6-22: Graphical view of editBox attributes (part 1)
Figure 6-23: Graphical view of editBox attributes (part 2)
Using Built-in editBox Controls Interestingly enough, there does not appear to be a way to customize any built-in editBox controls in Excel, Access, or Word. However, as you can see in the previous two figures, we have extensive opportunities for creating and customizing our own editBox controls.
Creating Custom Controls As mentioned, the editBox can provide an efficient way to obtain input from a user. The following examples walk you through a couple of popular uses, such as to change a file or worksheet name. By now you are familiar with many of the individual steps so they aren’t repeated in the examples. However, you can always get a refresher by following the detailed processes described earlier in this chapter.
An Excel Example For this example, assume that you are building an application within Excel that takes control of the entire user interface. This is actually fairly common in practice. It involves hiding every built-in facet of the provided UI, and replacing the standard UI
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 201
Chapter 6
■
RibbonX Basic Controls
with a custom setup that limits the user to only those actions that you explicitly allow. There are some specific Ribbon tricks for doing this, which are covered in later chapters, but for now you’ll focus on giving the user a way to rename a worksheet. You’ll start, once again, by going into Excel and creating a new Excel file. Save it in a macro-enabled (.xlsm) format, as you’ll need to rely on macros to work your magic. With the Excel file open, the next step is to build your macro so that you can link it up to the editBox later. For now, create VBA code similar to the following, and store it in a standard module: Private Function shtRename(sCallSheet As String) As Boolean On Error Resume Next ActiveSheet.Name = sCallSheet If Err.Number = 0 Then shtRename = True End Function Public Sub RenameSheet() Dim sNewSheetName As String sNewSheetName = InputBox(“Please enter a new name for the sheet.”) If shtRename(sNewSheetName) = False Then MsgBox “There was a problem and I could not” & vbCrLf & _ “rename your sheet. Please try again.”, _ vbOKOnly + vbCritical, “Error!” End If End Sub
It’s important to understand the purpose of not only these two routines but also the individual parts. Of course, these examples can also be enhanced, such as to provide more elegant error messages. The focus here, however, is on working with the editBox. ■■
shtRename is a function that renames the sheet. On Error is used to allow the routine to continue even if an error is encountered in the process. A function was used here, as it can test whether an error was encountered and return a true/false statement indicating whether the renaming was successful.
■■
RenameSheet then calls the function and tests whether it was successful. If it
wasn’t, it informs the user. To test this, run the RenameSheet macro and give it a name when prompted. Notice that your worksheet will be renamed. If you supply an invalid parameter, however, such as an existing sheet name, you will get an error message. Now that you have some functional code, save and close the file, and then open it in the CustomUI Editor. Again, because the idea is to be running these customizations without relying on built-in tabs or groups, you need to supply your own. Apply the RibbonBase template, and insert the following XML code between the and tags:
T I P Now you will notice that even though there is no user interface to work with, the code still specifies the custom tab’s position before the Home tab. This is OK, as the built-in tabs are merely hidden, not completely destroyed, when you create a new user interface from scratch.
T I P When working with the sizeString attribute, it can be helpful to type out the number of characters with incrementing numbers so that you can quickly determine the number of characters allowed. As shown in the example, “123456789012345” is 15 characters, and “123456789012345678901” would be 21 characters. Now validate your XML, copy the callback signature, and save and close the file. Upon reopening the file in Excel, you should now see the editBox on your custom tab, as shown in Figure 6-24.
Figure 6-24: editBox example in Excel
You have not yet implemented the callbacks, so the editBox obviously won’t work. Therefore, jump straight into the VBE, locate the standard module, and paste in the callback signature. In reviewing the VBA that was written earlier, you can see that you could not call this procedure directly from the callback. While that worked well in other examples, in this case we needed to determine whether the name already existed and check for other errors that would require the user to provide the sheet name twice. In order to accomplish our objective, we used the callback to run a series of functions. This may seem a little complex right now, but it is an important technique for you to become familiar with. To demonstrate this, put the following line in your callback and give it a test. Just don’t forget to remove it once you’re convinced: Call RenameSheet
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 203
Chapter 6
■
RibbonX Basic Controls
Fortunately, the onChange callback actually passes the text value that the user typed into the editBox. Now you have a couple of options. You could opt to pass the user’s response as a parameter to the already written routine, or you could just integrate the required portions right in the callback. The latter sounds like a much better and more direct approach, so that will be your next step. You want to copy the contents of the RenameSheet procedure into the callback, but make sure that you do not copy the following two lines: Dim sNewSheetName As String sNewSheetName = InputBox(“Please enter a new name for the sheet.”)
Those two lines were used to ask the user for a new worksheet name, before you had the editBox at your disposal. The user can now input the new sheet name right in the editBox before the procedure is run, so you don’t need those lines at all. The only problem that causes is that the callback now refers to the sNewSheetName variable, which has not been declared. You need to update the name to refer to the string that is passed to the callback: text. The adjusted callback will look as follows: ‘Callback for rxtxtRename onChange Sub rxtxtRename_Click(control As IRibbonControl, text As String) If shtRename(text) = False Then MsgBox “There was a problem and I could not” & vbCrLf & _ “rename your sheet. Please try again.”, _ vbOKOnly + vbCritical, “Error!” End If End Sub
Once you have your callback set up like the preceding example, jump out of the VBE, save the file, and click the My Tools tab. Type in a new name, and watch as the worksheet tab is updated.
A Word Example In the previous Word example, you built a checkBox control to show and hide the Style Inspector. One thing that was lacking, however, was a way to easily set the width. The editBox is the perfect control for that purpose, so in this example you’ll learn how two controls can be set on a Ribbon group to interact with and complement each other. You’ll keep the checkBox control in place, but also add an editBox to the Ribbon group. The purpose of the editBox will be to give users a place to enter whatever width they’d like to see. To get started, open the Word editBox example file that you worked on earlier in this chapter and save it under a new name. (Why reinvent the wheel if you already have half the code, right?) Remember to keep it in a .docm format, as you will still need to use the macros.
203
91118c06.qxd:WileyRedTight
204
Part 1
■
11/30/07
5:25 PM
Page 204
The Building Blocks for a Successful Customization
Now open the new file in the CustomUI Editor and add the following XML code right before the tag:
Again, validate the XML, save the file, and copy the rxtxtStyleWidth callback signature. Close the document in the CustomUI Editor and open it in Word. If you activate the View tab, you’ll see your UI modifications, which should look the same as what is shown in Figure 6-25. The Custom Options group now has the Width editBox below the Style Inspector checkBox.
Figure 6-25: The Style Inspector Width editBox
Now that you’ve satisfied your curiosity about the new look, it’s time to program your callbacks to fire. Head back into the VBE and paste your callback signature into the standard module that holds the rest of your Ribbon code. The next step, if you haven’t already thought this through, is to determine how the editBox control will interact with the existing checkBox control. You know that users are going to enter the width into the editBox, so what happens when they do this? In addition, what if they enter a value when the checkBox isn’t true? To deal with these eventualities, you need to save that value for later access. Obviously, this isn’t an exhaustive list of potential actions that a user could take, but it illustrates the need to think through a wide gamut of possibilities. It would be so much easier to write code if only we could control the user, but we do what we can to predict and provide accordingly. For now, you’ll learn a fundamental step, which is to store a value from the editBox. To do this, set up a new global variable at the top of your module (just under the Option Explicit line, which is at the top of all modules if you are following our recommended best practices): Dim dblStyleAreaWidth as Double
T I P Setting this up as a Double, unlike a Long or an Integer data type, enables you to hold decimal values. This is important, as not all users will want to use round numeric increments as their Inspector widths.
Next, look at your new callback signature and modify it to read as shown here: ‘Callback for rxtxtStyleWidth onChange Sub rxtxtStyleWidth_change(control As IRibbonControl, text As String)
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 205
Chapter 6
■
RibbonX Basic Controls
On Error Resume Next dblStyleAreaWidth = CDbl(text) If Err.Number 0 Then MsgBox “Sorry, you must enter a numerical value in this field!” text = “” Else ActiveWindow.StyleAreaWidth = InchesToPoints(dblStyleAreaWidth) End If End Sub
This might look a little confusing, but it breaks down as follows: The text from the editBox is passed to the routine and converted to a Double by way of the CDbl method.
The reason why error handling was activated in the previous line, however, is to check whether this failed. If it did, then the user is informed that they did not enter a number, and the editBox is cleared. If the value was numerical, no error would be triggered, so the width is set to the value. Keep in mind that if the checkBox is not set to true, then nothing will appear to happen. In fact, every time the number is changed in the editBox, the value is passed to the dblStyleAreaWidth variable and stored there for you. The value is stored, but there is still one small thing you need to do before your function will work as intended. You might be wondering how the value is pulled from that variable when you need it. The answer, currently, is that it isn’t. You need to modify the ShowStyleInspector routine, programmed in the previous example, to make that happen. Therefore, take a look at that routine and change the first line to read as follows: ActiveWindow.StyleAreaWidth = InchesToPoints(dblStyleAreaWidth)
That’s all you need to do here. Now you can close the VBE, save the file, and start to play with the controls. Enter a new number in the editBox, and check and uncheck the checkBox. The Style Inspector window will react to your requests.
N OT E This setup does not deal with a couple of things. First, the Style Inspector width can only go so far. Currently, if you change it to a number above the maximum threshold, calculated as half of the points value of the ActiveWindow.UseableWidth, it just won’t do anything. Second, if a user were so inclined, they could still go into the Word Options screen to set this width property and it would not be reflected in your Ribbon controls. However, the chances of this approach are quite slim.
An Access Example This example again builds on the previously illustrated database used in the last section, so if you need a refresher on the detailed steps, you can review the process there as well. Here, you’ll add the capability to rename forms from the Ribbon by specifying the old form name and then providing a new name. This involves using two editBox controls, and invalidating the UI.
205
91118c06.qxd:WileyRedTight
206
Part 1
■
11/30/07
5:25 PM
Page 206
The Building Blocks for a Successful Customization
To get started, create a copy of the project you were working on for the checkBox example (or download the complete checkBox example from the website) and open the new file. Create a new form in the database project. We’ll use this new form to test the renaming procedure. Next, navigate to the USysRibbons table and copy the XML code from the RibbonXML field into a new instance of the CustomUI Editor. You need to add two new editBoxes to this project, and the most sensible place seems to be under the Show Systems Objects checkBox that you created earlier. To add the editBoxes, insert the following XML just before the last line:
Now you need to add one more piece to your XML in order to be able to invalidate the UI. Recall that invalidating the UI causes the entire Ribbon to reload so that it will incorporate the new objects. To accomplish this, you must capture the IRibbonUI object, which requires you to modify the opening CustomUI tag to read as follows:
Once you’ve done all this, validate the XML, and then copy all the code from the CustomUI Editor. Paste this back into the RibbonXML field in your database, replacing the code that was in the field. Now, head back into the CustomUI Editor, generate the callbacks, and copy the following callbacks into the modRibbonX module in the Access Project: rxIRibbonUI_onLoad rxtxtRenameFrom_getText rxtxtRenameFrom_change rxtxtRenameTo_getText rxtxtRenameTo_change
Before you start modifying these routines, you need to set up two global variables. This is done by placing the following two lines just beneath the Option lines that should appear at the top of the module, as mentioned earlier: Dim RibbonUI As IRibbonUI Dim sRenameFrom As String
The purpose of the first variable is to hold the RibbonUI object in memory so that you can invalidate it, forcing the Ribbon controls to be rebuilt. The second variable will
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 207
Chapter 6
■
RibbonX Basic Controls
hold the name of the form that you wish to rename. This variable is important, as it can be referenced in other routines that need to use the value. Now that the global variables are set up, it’s time to modify the callback signatures. The first is the onLoad signature that captures the ribbon object. It should look as follows: ‘Callback for customUI.onLoad Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set RibbonUI = ribbon End Sub
Next, you want to deal with the rest of the callbacks. They should look as follows: `Callback for rxtxtRenameFrom getText Sub rxtxtRenameFrom_getText(control As IRibbonControl, _ ByRef returnedVal) returnedVal = sRenameFrom End Sub ‘Callback for rxtxtRenameTo getText Sub rxtxtRenameTo_getText(control As IRibbonControl, ByRef returnedVal) returnedVal = “ “ End Sub ‘Callback for rxtxtRenameFrom onChange Sub rxtxtRenameFrom_change(control As IRibbonControl, text As String) On Error Resume Next sRenameFrom = CurrentProject.AllForms(text).Name If Err.Number 0 Then ‘Table does not exist, so clear the editBox MsgBox “That table does not exist!” sRenameFrom = “ “ RibbonUI.InvalidateControl (“rxtxtRenameFrom”) End If On Error GoTo 0 End Sub ‘Callback for rxtxtRenameTo onChange Sub rxtxtRenameTo_change(control As IRibbonControl, text As String) Dim sOldName As String On Error Resume Next ‘Check if from name is valid sOldName = CurrentProject.AllForms(sRenameFrom).Name If Err.Number 0 Then MsgBox “Specify a valid table to rename first!” GoTo EarlyExit End If ‘Attempt to rename the table to new name DoCmd.Rename text, acForm, sOldName
207
91118c06.qxd:WileyRedTight
208
Part 1
■
11/30/07
5:25 PM
Page 208
The Building Blocks for a Successful Customization
If Err.Number 0 Then MsgBox “Sorry, that is an invalid name. ” _ & “Please enter a different name” GoTo EarlyExit End If ‘Rename was successful, so clear the editBoxes sRenameFrom = “ “ RibbonUI.InvalidateControl (“rxtxtRenameFrom”) RibbonUI.InvalidateControl (“rxtxtRenameTo”) EarlyExit: On Error GoTo 0 End Sub
These routines are built on the following methods: ■■
The rxtxtRenameFrom_getText routine returns the text displayed in the RenameFrom text box. Every time this routine is called, whether that’s at the time the tab is initially activated or when the control has specifically been invalidated, it will check the value (if any) that is held in the global variable sRenameFrom and place the value in the editBox.
■■
The rxtxtRenameTo_getText callback is very similar to the above, but it does not use a global variable to hold a value. It simply returns a blank space every time it is called. This may seem strange, but you’ll never need to store a value for this editBox. If the renaming is successful, the editBox rxtxtRenameTo_getText is cleared by invalidating it. If it is not successful, then the user must reconfirm the value in this editBox to fire its code, so storing the value is not necessary.
N OT E If you’ve done any programming in VBA, you’ll find the use of the blank space a little jarring. To conform to best practices, most coders would never commit such an atrocity to clear a value from a control. They would instead provide the vbnullstring constant, which equates to a Null value. Unfortunately, XML requires that any string data type, of which the editBox text length is one, be at least one character long. The rxtxtRenameFrom_change routine uses a trick to test whether the form name supplied is valid. It checks by asking for the supplied form’s name. If it can be provided, then the form must exist. If not, then the user must have provided an invalid form because every form has a name. In the case of a user providing an invalid name, the control is cleared by invalidating it. The rxtxtRenameTo_change routine is the most complicated routine in the set. It first checks to ensure that the form the user has asked to be renamed exists. This is necessary in case the first editBox has been left blank. If this test passes successfully, then the routine tries to rename the form to the value provided by the user in the rxtxtRenameTo editBox. Because it is possible for a user to provide an invalid name
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 209
Chapter 6
■
RibbonX Basic Controls
(one reserved by Access, that of an existing form, or one that uses forbidden characters), the code checks whether an error was encountered during the renaming process. If so, then the user receives an error message and the editBox is cleared. If there was no error and this test passes, the renaming was successful, and therefore both editBoxes are cleared by invalidation. Finally, you are ready to save the VBA project. You need to close and reload your database so that the RibbonUI modifications can take effect. In your new UI, type the name of the temporary table that you created in the first editBox, and the new name that you’d like to call it in the second editBox, as shown in Figure 6-26.
Figure 6-26: Renaming a form
Upon pressing the Enter key, you’ll see that the frmTemp form will be renamed to the name that you provided. Try providing an invalid name in the first editBox, or try to give the form the same name of an existing object, such as repeating a table name. Note how the error handling informs the user about the issue.
The toggleButton Element A toggleButton is used to alternate between states — such as turning some feature on and off — when it is pressed. For example, you could use this button to turn the ruler in Word on and off, or to switch from the Page Break view in Excel, or to hide/unhide a form in Access. Whatever way you use it, you are alternating between two possible states of an object, control, and so on.
Required Attributes of the toggleButton Element The toggleButton requires any one of the id attributes listed in Table 6-13.
209
91118c06.qxd:WileyRedTight
210
Part 1
■
11/30/07
5:25 PM
Page 210
The Building Blocks for a Successful Customization
Table 6-13: Required Attributes of the toggleButton Element ATTRIBUTE
WHEN TO USE
id
When creating your own toggleButton
idMso
When using an existing Microsoft toggleButton
idQ
When creating a toggleButton shared between namespaces
The toggleButton also requires the onAction callback shown in Table 6-14. Table 6-14: Required Callback of the toggleButton Element DYNAMIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE
onAction
1 to 4096 characters
Sub OnAction (control As IRibbonControl, selectedId As String, selectedIndex As Integer)
Optional Static and Dynamic Attributes with Callback Signatures The toggleButton may have one of the insert attributes shown in Table 6-15. Table 6-15: Optional insert Attributes of the toggleButton Element INSERT ATTRIBUTE ALLOWED VALUES DEFAULT VALUE WHEN TO USE insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control
insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
In addition, it may have any or all of the attributes listed in Table 6-16.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 211
Chapter 6
■
RibbonX Basic Controls
Table 6-16: Optional Attributes and Callbacks of the toggleButton Element STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
description
getDescription 1 to 4096 characters
enabled
getEnabled
image
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
(none)
Sub GetDescription (control As IRibbonControl, ByRef returnedVal)
true, false, 1, 0
true
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
getImage
1 to 4096 characters
(none)
Sub GetImage (control As IRibbonControl, ByRef returnedVal)
imageMso
getImage
1 to 4096 characters
(none)
Same as above
keytip
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 4096 characters
(none)
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
(none)
getPressed
true, false, 1, 0
(none)
Sub GetPressed(control As IRibbonControl, ByRef returnedVal)
screentip
getScreentip
1 to 4096 characters
(none)
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal)
showImage
getShowImage true, false, 1, 0
true
Sub GetShowImage (control As IRibbonControl, ByRef returnedVal)
showLabel
getShowLabel true, false, 1, 0
true
Sub GetShowLabel (control As IRibbonControl, ByRef returnedVal)
size
getSize
normal
Sub GetSize (control As IRibbonControl, ByRef returnedVal)
normal, large
Continued
211
91118c06.qxd:WileyRedTight
212
Part 1
■
11/30/07
5:25 PM
Page 212
The Building Blocks for a Successful Customization
Table 6-16 (continued) VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
supertip
getSupertip
1 to 4096 characters
(none)
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 4096 characters
(none)
(none)
visible
getVisible
true, false, 1, 0
true
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
Allowed Children Objects of the toggleButton Element The toggleButton does not support any child objects.
Parent Objects of the toggleButton Element The toggleButton control can be placed within any of the following controls: ■■
box
■■
buttonGroup
■■
dynamicMenu
■■
group
■■
menu
■■
officeMenu
■■
splitButton
Graphical View of toggleButton Attributes Figure 6-27 is a sample customization that displays all of the visible graphical attributes that you can set on the toggleButton control.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 213
Chapter 6
■
RibbonX Basic Controls
Figure 6-27: Graphical view of toggleButton attributes
Using Built-in toggleButton Controls Quite a few built-in toggleButton controls are used by Microsoft in the Ribbon. They range from font styles to borders to paragraph alignment tools. This example adds four built-in toggleButtons to a custom tab. It includes one of the controls not prominent in the Ribbon, despite being a built-in control. Create a new Word file to house the example. You won’t need any macros for this example, so a .docx file type will work just fine. After saving the file, open it in the CustomUI Editor, apply the RibbonBase template, and insert the following XML code between the and tags:
Once you’ve validated your code, save the file and open it in Word. After clicking the My Tools tab, you should be looking at the group shown in Figure 6-28. It can be a little frustrating to have the label displayed, but it helps to illustrate the next point.
213
91118c06.qxd:WileyRedTight
214
Part 1
■
11/30/07
5:25 PM
Page 214
The Building Blocks for a Successful Customization
Figure 6-28: The My Tools tab in Word
Although there are consistencies between the applications, there are also some inconsistencies present. Try creating a new Excel file and using the exact same XML code to create your new UI. The result will look like what is shown in Figure 6-29.
Figure 6-29: The My Tools tab in Excel
Notice how Excel hides the caption for the Double Underline, whereas Word prominently displays it. Hiding the caption was covered in the button control example at the beginning of the chapter, so you already know how to do that. The point is to illustrate some of the nuances that you should be aware of. Before moving into the following examples, try typing some text in your document (or worksheet) cell and playing with the formatting controls. They will toggle on and off with the setting. Isn’t it reassuring that it is becoming relatively easy to insert a customization that functions just as you’d expect?
Creating Custom Controls It is now time to look at some examples of creating your own custom controls in Excel, Word, and Access. The Word example provides a new tool that enables you to use image placeholders, rather than store the entire image in a document. The Excel and Access examples continue to build upon previous examples by adding extra functionality.
An Excel Example To show off the features of the toggleButton, this example revisits the Prepaid Expense schedule that was used earlier in this chapter in the custom button example. We’ll add the capability to turn a custom view on or off, thereby allowing users to show or hide the Expense To column, shown in Figure 6-30.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 215
Chapter 6
■
RibbonX Basic Controls
Figure 6-30: Prepaid Expense schedule showing the Expense To column
Are you ready to get started? Open the previous example, or use the file buttonMonthly Roll Forward.xlsm from the book’s website. Save the file with a new name, but don’t head over to the CustomUI Editor quite yet. There’s a bit of work to be done here first. It’s time to set up the custom views that you will need to make this work. Therefore, before you do anything else, go to the View tab and click Custom Views. Select Add and call the view cvw_Show. This view will be used to return to a full view of the worksheet, with all columns showing. Next, you’ll want to set up the second view that will hide the Expense To column. To do that, click Close to close the Custom Views window and return to the main Excel interface, and then hide column F. You’ll notice now that your cursor is missing, and that the line between columns E and G seems to be selected. This is a cosmetic issue that can be resolved by selecting cell A1, and then returning to Custom Views. This time, add a new custom view called cvw_Hide. Close the Custom Views window again.
T I P If you make a mistake when setting up your view, you can replace it by setting up a new view the way you like it and then saving it with the name of the view that you want to replace. Now it’s time to record the macro to toggle the views on and off. Click the Record Macro button, which is found on both the status bar and the Developer tab. With the recorder on, use the following steps to record the process: 1. Go back to the View tab. 2. Click Custom Views. 3. Select the cvw_Hide view and choose Show. 4. Click Custom Views again. 5. Select the cvw_Show view and choose Show. 6. Go back to the Developer tab and stop the Macro recorder. You may be wondering why you did this. The answer will become obvious when you open the VBE and look at the code that was recorded. You’ll see the following: ActiveWorkbook.CustomViews(“cvw_Hide”).Show ActiveWorkbook.CustomViews(“cvw_Show”).Show
215
91118c06.qxd:WileyRedTight
216
Part 1
■
11/30/07
5:25 PM
Page 216
The Building Blocks for a Successful Customization
Notice that it generated only two lines, which are fairly self-explanatory. These will come in very handy in a moment, when you are programming the callbacks. Your next step, though, is to set up the new toggleButton on the Ribbon. Save your file, close it in Excel, and open it in the CustomUI Editor. Insert the following XML just before the tag:
As always, validate the code, save the file, and remember to copy the new rxtglHideExpense_Click callback signature before closing the file. Open the file in Excel, jump into the VBE, and paste the callback signature into the standard module that contains the existing code. It’s now time to modify the callback to do what you need. Once again, you’ll want to set up a Select Case statement to evaluate which action to take when the button is clicked. Like the checkBox, the RibbonX code passes a parameter named pressed to the routine when it is fired. This makes your job very easy, as you merely need to paste the appropriate lines into the case statement, as shown here: `Callback for rxtglHideExpense onAction Sub rxtglHideExpense_Click(control As IRibbonControl, _ pressed As Boolean) Select Case pressed Case True ActiveWorkbook.CustomViews(“cvw_Hide”).Show Case False ActiveWorkbook.CustomViews(“cvw_Show”).Show End Select End Sub
Now that the callback is programmed and completely operational, head back to the Excel interface and click the Demo tab. Congratulations! You’ve successfully added automation. Note that when the sheet opens, the toggleButton is not highlighted and column F is visible. However, clicking the Hide Expenses button changes the view to that shown in Figure 6-31.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 217
Chapter 6
■
RibbonX Basic Controls
Figure 6-31: Prepaid Expense schedule under mask of a custom view
Clicking the Hide Expenses button again will display Column F, and revert the Hide Expenses toggleButton to its unselected state, as shown in Figure 6-32.
Figure 6-32: Hide Expenses toggleButton in its unselected state
You should be aware that this example is not set up to deal with the issue caused when the workbook is saved with the cvw_Hide view active. In that case, when the workbook was opened, the toggleButton would show unselected, but the view with the hidden column would be active. A couple of clicks by the user would resolve the disparity, but it’s better to manage this with code. There are two ways to fix this issue: ■■
Set up a Workbook_Open procedure that sets the cvw_Show view to active when the workbook is opened.
■■
Set up the getPressed callback to test which view is active when the workbook is opened and return that state to the toggleButton.
A Word Example If you work with long documents that contain numerous images, you will likely perceive a performance hit. Fortunately, there is a setting in Word that allows us to show placeholders for pictures, rather than render the entire image. Logically, this should
217
91118c06.qxd:WileyRedTight
218
Part 1
■
11/30/07
5:25 PM
Page 218
The Building Blocks for a Successful Customization
provide a noticeable improvement in response time, and make users rather happy. The toggleButton is the ideal control for turning this setting on and off. Since the previous example started a collection of custom tools, we’ll add the toggleButton to the same group, as shown in Figure 6-33.
Figure 6-33: Using a toggleButton control
N OT E If you want to create this in a new file, feel free to do so, as none of the VBA routines overlap. Be aware that you’ll also need to create the XML framework. Start by saving a new copy of the example file from the previous illustration (or open a copy from the example files available at the website for this book). Make sure that the file is a macro-enabled (.docm) file, as this will require a little more work in the VBE. Before you move to the XML arena, we recommend that you start in Word and record a macro that will do what you are trying to accomplish — that is, change the ShowPicturePlaceholders property. After all, if the VBA macro doesn’t work, all the XML code in the world won’t do any good. In the Word file, start the Macro recorder. Once you’ve given the recorder a name for the macro, go to the Office button, choose Word Options, and click the Advanced button. Under the Show Document Content header, you’ll find the setting for Show Picture Placeholders. Check the box, say OK, and stop the macro from recording. If you now check the VBE, you’ll see that your code looks like the following: ActiveWindow.View.ShowPicturePlaceHolders = True
This is fantastic, as it provides a short, concise, and fairly intuitive code snippet. You can easily change this value by setting it to true or false. Moreover, you can test it to determine the current value. Confident that the code that will work, you can now set the XML markup in the CustomUI. Save the file, open it in the CustomUI Editor, and then insert the following XML just before the tag:
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 219
Chapter 6
■
RibbonX Basic Controls
There is just one more step in this part of the process. In this example, you want to invalidate a control, which means that you need to capture the ribbon object to a variable. To do this, you must include an onLoad statement in your XML. Therefore, at the top of the XML code, modify the opening tag to read as follows:
As always, before you close the file in the CustomUI Editor, you need to validate the code. In addition, you also need to copy all three rxtglPicHold callback signatures and the rxIRibbonUI_onLoad signature. Because activating your tab will generate missing callback errors, you might as well skip that for now and head straight into the VBE. Paste your callback signatures into the standard module that holds the rest of the code. Now it’s time to set the groundwork for linking your file together. First, create a variable to hold the RibbonUI object. At the top of your module, just under the Option Explicit line, insert the following line: Private ribbonUI As IRibbonUI
Next, update your callback signatures. Because there are several signatures, it’s best to review them one at a time. As we go through them in order and explain what each one does, keep in mind that all four functions will appear together in your code. The first routine is triggered at load time, and captures the ribbon object to the ribbonUI variable for later use: Private Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) ‘Store the ribbonUI for later use Set ribbonUI = ribbon End Sub
The getPressed routine, shown in the following code, is fired when the tab is first activated, and attempts to determine whether the control should show as pressed or not. In this case, it evaluates whether the ShowPicturePlaceHolders setting is true or false, and passes that back to the returnedVal parameter: Public Sub rxtglPicHold_getPressed(control As IRibbonControl, _ ByRef returnedVal) `Callback for rxtglPicHold getPressed returnedVal = _ ActiveDocument.ActiveWindow.View.ShowPicturePlaceHolders End Sub
The getImage routine was inserted to update the picture on the toggleButton depending on the state of the setting. If it is true, then it uses one picture (SignatureInsertMenu); and if it is false, then it displays the DesignMode image. This routine isn’t strictly necessary, but it is an relatively easy way to jazz up your controls: Public Sub rxtglPicHold_getImage(control As IRibbonControl, _ ByRef returnedVal)
219
91118c06.qxd:WileyRedTight
220
Part 1
■
11/30/07
5:25 PM
Page 220
The Building Blocks for a Successful Customization
‘Callback for rxtglPicHold getImage Select Case ActiveDocument.ActiveWindow.View.ShowPicturePlaceHolders Case True returnedVal = “SignatureInsertMenu” Case False returnedVal = “DesignMode” End Select End Sub
Last but not least is the actual Click routine. This is the routine that really controls what’s going on. Whenever it is clicked, it sets the ShowPicturePlaceHolders property to the toggleButton’s pressed state. (Remember that each click toggles the pressed state between true and false.) In addition, it invalidates the control, forcing the Ribbon to rerun the getImage and getPressed routines. By running those two routines, it can toggle the image with each press of the toggleButton. Public Sub rxtglPicHold_click(control As IRibbonControl, _ pressed As Boolean) `Callback for rxtglPicHold onAction ActiveDocument.ActiveWindow.View.ShowPicturePlaceHolders = pressed ribbonUI.InvalidateControl “rxtglPicHold” End Sub
It’s time to save and reload the document. Give it a try and take a moment to appreciate your handiwork!
An Access Example This example builds on the prior database by adding some really neat functionality. Specifically, you’ll build a group that only shows up when a specific form is opened, a truly contextual group. The new group will provide the capability to toggle between the DataSheet and Normal views. To get started, make another copy of the database that we were working with in the previous example. Open the file and then copy the RibbonUI code from the USysRibbons table and paste it into the CustomUI Editor. Because you will typically want to hide this entire group, you’ll need to create a brand-new group. It makes the most sense to put the new group between the two existing groups, so paste the following code between the and lines:
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 221
Chapter 6
■
RibbonX Basic Controls
Validate the code to ensure that it was typed correctly, and then copy the entire code listing, and paste it back into the RibbonUI field in your database. You’ll then need to go back and copy the new callbacks into your modRibbonX VBA project as well. This time, you’ll be copying the following: rxgrpFormTools_getVisible rxtglViewDataSheet_click
Again, you need to set up a global variable, so just underneath the existing ones, add the following: Public bShowFormTools As Boolean
The purpose of the variable is to hold the state indicating whether the form is currently active, which in turn will determine whether the new group should be visible or not.
N OT E Because this will be referred to from another module later, it should be marked as public. Public variables can be “seen” from the other modules. In addition, the RibbonUI variable should also be marked as a public variable. Now it’s time to program your callbacks. Add code to the signatures so that they look as follows: `Callback for rxgrpFormTools getVisible Sub rxgrpFormTools_getVisible(control As IRibbonControl, _ ByRef returnedVal) returnedVal = bShowFormTools End Sub `Callback for rxtglViewDataSheet onAction Sub rxtglViewDataSheet_click(control As IRibbonControl, _ pressed As Boolean) `Open the correct view Select Case pressed Case True DoCmd.OpenForm “frmAuthors”, acFormDS Case False DoCmd.OpenForm “frmAuthors”, acNormal End Select End Sub
The getVisible callback pulls its value from the global variable the first time the group is activated, as well as whenever it is invalidated. The rxtglViewDataSheet_click routine, however, is a little more complex. It checks the state of the toggleButton, (true for clicked, false if not), and opens the form in Data Sheet or Normal View as appropriate. It also toggles the Show Totals variable to ensure that it will be hidden if in Normal View. By invalidating the control after the Show Totals variable is set, you know that it will always be in the correct state.
221
91118c06.qxd:WileyRedTight
222
Part 1
■
11/30/07
5:25 PM
Page 222
The Building Blocks for a Successful Customization
At this point, you may be wondering how this toggleButton ever shows up at all. The answer is that it will be triggered by launching the Enter Authors form. To do this, you need to add some event code that will be triggered by various form actions. The easiest way to set up the required code is to open the form and set it to Design View. (You can change the view via the View button on the Home tab.) Once there, on the Design tab, click the Property Sheet button on the Tools group. Next, click the Event tab of the Property Sheet window, and then in the blank space next to the On Activate line. You’ll notice that a little drop-down arrow and an ellipses (. . .) button appear. From the drop-down arrow, select [Event Procedure], and then click the . . . button to be taken to the VBE. Notice that a new Forms module was created, and that you are now looking at an event procedure for the Forms object. Change this procedure to read as follows: Private Sub Form_Activate() bShowFormTools = True RibbonUI.Invalidate End Sub
MS Office forms have Activate and Deactivate events that fire each time the Ribbon UI is invalidated, so these are ideal events for controlling when a contextual group is visible or not. The preceding routine takes care of making sure that the group shows up when the form is activated, but you’ll also need to set up the Deactivate event. On the left side of the code pane, Form is selected; the right side shows Activate. On the right drop-down, select Deactivate, and notice how the event signature is placed in your code module. Adjust it to read as follows: Private Sub Form_Deactivate() bShowFormTools = False RibbonUI.Invalidate End Sub
These routines toggle the bShowFormTools values and invalidate the entire Ribbon in order to show or hide the group when the form is active or inactive, respectively.
T I P In order to get a group’s callbacks to fire, you may have to invalidate the entire Ribbon, instead of just the individual control.
N OT E Because there are rare instances when the Activate and Deactivate events aren’t triggers, some developers might be tempted to use the onLoad and onClose events. However, these would leave the custom group visible when the user moves from one form to another without closing the “target” form. Now it’s time to try it out. Save everything, and then close and reload your database. When you click the Enter Authors button, the form will still launch, and you should see the My Tools tab with the Form Tools group, as shown in Figure 6-34.
91118c06.qxd:WileyRedTight
11/30/07
5:25 PM
Page 223
Chapter 6
■
RibbonX Basic Controls
Figure 6-34: The visible Form Tools group
Now try clicking the Show DataSheet View toggleButton; both the form’s display and the Ribbon group will change to appear as shown in Figure 6-35.
Figure 6-35: DataSheet view
Conclusion This chapter provides several reference tables so that you can conveniently find the attributes and callback signatures associated with the various Ribbon controls. We also included images to illustrate where you can add visible attributes. Our examples demonstrated a variety of required and optional features so that you will have the experience and confidence to expand and extrapolate the processes to work with your projects. We covered many of the most popular controls, such as the button, checkBox, editBox, and toggleButton. As the examples began to build on one another, you were exposed to just some of the richness that can be attained by using
223
91118c06.qxd:WileyRedTight
224
Part 1
■
11/30/07
5:25 PM
Page 224
The Building Blocks for a Successful Customization
Ribbon controls, both alone and in concert with other controls. Even with the variety of examples provided here, we’ve only begun to explore the limits of these and other controls. By now you are likely realizing that the appearance of your UI is limited only by your own imagination. Now that you have a firm understanding of the most common Ribbon controls, you are ready to move into some more robust features. Chapter 7 is the next step, as it focuses on the dropDown and comboBox elements.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 225
CHAPTER
7 comboBox and dropDown Controls
In the previous chapter, you learned about the button, checkBox, editBox, and toggleButton controls. This chapter explores two new controls: the comboBox and the dropDown. The comboBox and dropDown list are similar in a great many ways, including design, implementation, and appearance. They also have some important differences that are discussed in this chapter. Before you can start exploring these two controls, you need to learn about the fundamental element that supports them: the item element. The chapter begins by exploring this critical piece. Following the section on the item element, you’ll find both the comboBox and dropDown sections, which explore these two elements in great detail. As in the previous chapter, examples are included for each application, some of which display the creation of static versions of controls, while others create fully dynamic versions. Whether you’re working through the examples or just reading the chapter, you will appreciate seeing a fully functioning version. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
The item Element The item element is used to create static items that must be used within a gallery, dropDown, or comboBox. This particular element is not intended for use on its own, but rather must be an integral part of other controls, such as those mentioned above.
225
91118c07.qxd:WileyRedTight
226
Part I
■
11/28/07
9:16 PM
Page 226
The Building Blocks for a Successful Customization
Unlike other sections of this book, where we provide full working examples of the element being discussed, we review only the XML construct and discuss the structure of the item element. This departure from the pattern reflects the fact that the item element is so tightly integrated with the comboBox, dropDown, and gallery controls that it cannot be separated from them. Because of this, you need to know a little bit about item before you can move on. The way these are used may seem rather complicated for now, but rest assured that the processes will become surprisingly clear when you examine the comboBox and dropDown RibbonX elements later in this chapter.
Required Attributes of the item Element Each item requires a unique id attribute, as described in Table 7-1. Table 7-1: Required Attribute of the item Element ATTRIBUTE
WHEN TO USE
id
Use this attribute to create your own item.
The item element has only one attribute, the id. As we just mentioned, the item control must be used in conjunction with other elements; therefore, it relies on the other elements for all other attributes.
N OT E Unlike other elements, there is no idMso or idQ attribute available for the item control.
Optional Static and Dynamic Attributes with Callback Signatures Each item element can optionally make use of any or all of the attributes shown in Table 7-2. Table 7-2: Optional Attributes for the item Element STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
image
(none)
1 to 1024 characters
(none)
(none)
imageMso
(none)
1 to 1024 characters
(none)
(none)
label
(none)
1 to 1024 characters
(none)
(none)
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 227
Chapter 7
■
comboBox and dropDown Controls 227
Table 7-2 (continued) STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
screentip
(none)
1 to 1024 characters
(none)
(none)
supertip
(none)
1 to 1024 characters
(none)
(none)
Did you notice the lack of callback signatures in Table 7-2? It is important to understand that the items in the XML code underlying Ribbon modifications are always static. This is actually a good thing, as it means that you can provide a static list of items to the control without having to write a single line of VBA. Don’t misunderstand this to mean that you can’t create items for a control on-the-fly using VBA, as that is not the case. When you create an item for a control, the callback is actually associated with the Parent object (i.e., the comboBox, dropDown, or gallery control), not the actual item itself.
N OT E As you design your XML code, you are given a choice between using static item elements (specified in your XML), or dynamic elements (generated via the parent control’s callback signature.) Whichever route you choose is mutually exclusive of the other. In other words, if you specify static items for a control, you cannot also specify dynamic items for that control as well.
Allowed Children Objects of the item Element The item element does not support child objects of any kind, so it cannot have any embedded controls.
Parent Objects of the item Element An item may only be used in the following controls: ■■
comboBox
■■
dropDown
■■
gallery
Graphical View of item Attributes Figure 7-1 shows a dropDown control on a Ribbon group. It houses three static item elements that are written into the XML code. The callouts annotate where the item’s static attributes are displayed.
91118c07.qxd:WileyRedTight
228
Part I
■
11/28/07
9:16 PM
Page 228
The Building Blocks for a Successful Customization
Figure 7-1: Graphical view of the item elements
Using Built-in Controls You don’t have the opportunity to leverage built-in item elements because Microsoft does not expose them for our use, but the chances are fairly good that you wouldn’t want to use them anyway, so it’s not really a big loss. If you did want to have some of these items in a control, you could just include the entire parent element in your Ribbon.
Creating Custom Controls We explore the setup for the parent controls shortly, so this section focuses on the underlying XML code needed to create the static item elements demonstrated in Figure 7-1. As mentioned earlier, Figure 7-1 makes use of a dropDown control to house these items. The dropDown control should be constructed, like all controls, with opening and closing XML tags. The following code goes between those tags:
As you review Figure 7-1, you will see each of the elements in the live representation on the Ribbon.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 229
Chapter 7
■
comboBox and dropDown Controls 229
The comboBox Element The comboBox control displays data based on a designated record source, and it is a hybrid of the editBox that we covered in Chapter 6 and the dropDown control that we review next. One of the great features of a comboBox control is that, in addition to being able to pick something from the list, the user can also type something into the text box. In searching and selecting from the list, it acts like a “hot lookup,” enabling users to skip to the closest match. As the user keeps typing, the choices are narrowed down. At any time, users may accept one of the displayed values or they may keep typing to create a new entry. The comboBox is best used in the following situations: ■■
The list is very long and you wish to give users the capability to quickly jump to the appropriate place by typing a few keys. (The fonts control is a good example of this.)
■■
You wish to present users with a pre-defined list, but also want them to be able to add items to the list.
As mentioned earlier in the chapter, you can populate the comboBox with both static lists and dynamically created lists.
Required Attributes of the comboBox Element The comboBox control requires any one of the id attributes shown in Table 7-3. Table 7-3: Required id Attributes of the comboBox Element ATTRIBUTE
WHEN TO USE
id
When creating your own comboBox
idMso
When using an existing Microsoft comboBox
idQ
When creating a comboBox shared between namespaces
Optional Static and Dynamic Attributes withCallback Signatures In addition to the required id attribute, the comboBox control will optionally accept any one of the insert attributes listed in Table 7-4.
91118c07.qxd:WileyRedTight
230
Part I
■
11/28/07
9:16 PM
Page 230
The Building Blocks for a Successful Customization
Table 7-4: Optional insert Attributes of the comboBox Element INSERT ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
WHEN TO USE
insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control
insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
Finally, the comboBox may also be configured to use any or all of the optional attributes or callbacks shown in Table 7-5. Table 7-5: Optional Attributes and Callbacks of the comboBox Element VBA CALLBACK ALLOWED DEFAULT SIGNATURE FOR VALUES VALUE DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
(none)
onChange
1 to 4096 characters
(none)
Sub OnChange (control As IRibbonControl, text As String)
enabled
getEnabled
true, false, 1, 0
true
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
image
getImage
1 to 1024 characters
(none)
Sub GetImage (control As IRibbonControl, ByRef returnedVal)
imageMso
getImage
1 to 1024 characters
(none)
Same as above
(none)
getItemCount
1 to 1024
(none)
Sub GetItemCount (control As IRibbonControl, ByRef returnedVal)
(none)
getItemID
1 to 1024 characters
(none)
Sub GetItemID (control As IRibbonControl, index As Integer, ByRef id)
(none)
getItemImage
Unique text string
(none)
Sub GetItemImage (control As IRibbonControl, index As Integer, ByRef returnedVal)
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 231
Chapter 7
■
comboBox and dropDown Controls 231
Table 7-5 (continued) VBA CALLBACK ALLOWED DEFAULT SIGNATURE FOR VALUES VALUE DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
(none)
getItemLabel
1 to 1024 characters
(none)
Sub GetItemLabel (control As IRibbonControl, index As Integer, ByRef returnedVal)
(none)
getItem ↵ Screentip
1 to 1024 characters
(none)
Sub GetItemScreenTip (control As IRibbonControl, index As Integer, ByRef returnedVal)
(none)
getItemSupertip 1 to 1024 characters
(none)
Sub GetItemSuperTip (control As IRibbonControl, index As Integer, ByRef returnedVal)
keytip
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 1024 characters
(none)
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
maxLength
(none)
1 to 1024
1024
(none)
screentip
getScreentip
1 to 1024 characters
(none)
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal)
showImage
getShowImage
true, false, 1, 0
true
Sub GetShowImage (control As IRibbonControl, ByRef returnedVal)
showItem ↵ Attribute
(none)
true, false, 1, 0
true
(none)
showItemImage
(none)
true, false, 1, 0
true
(none)
showLabel
getShowLabel
true, false
true
Sub GetShowLabel (control As IRibbonControl, ByRef returnedVal)
sizeString
(none)
1 to 1024 characters
12*
(none)
supertip
getSupertip
1 to 1024 characters
(none)
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal) Continued
91118c07.qxd:WileyRedTight
232
Part I
■
11/28/07
9:16 PM
Page 232
The Building Blocks for a Successful Customization
Table 7-5 (continued) VBA CALLBACK ALLOWED DEFAULT SIGNATURE FOR VALUES VALUE DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
tag
(none)
1 to 1024 characters
(none)
(none)
(none)
getText
1 to 4096 characters
(none)
Sub GetText (control As IRibbonControl, ByRef returnedVal)
visible
getVisible
true, false, 1, 0
true
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
N OT E The default value for the sizeString attribute (if the attribute is not declared at all) is approximately 12, but this varies depending on the characters used and the system font.
Allowed Children Objects of the comboBox Element The only child object that can be used with the comboBox element is the item element.
Parent Objects of the comboBox Element The comboBox element may be nested within the following elements: ■■
box
■■
group
Graphical View of comboBox Attributes Figure 7-2 gives a graphical representation of the visible attributes that can be set on the comboBox control. Figure 7-2 shows all of the comboBox attributes except for the screentip and supertip. These two attributes only show when the dropDown list portion is not active; consequently, they could not be captured while showing the dropDown list.
Using Built-in Controls Of all the controls in Excel and Word, probably the best known is the Fonts comboBox. If you are creating custom Ribbon tabs to group your most commonly used controls together, then you will certainly want to add this control. It is therefore the first one that we look at.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 233
Chapter 7
■
comboBox and dropDown Controls 233
Figure 7-2: Graphical view of the comboBox elements
The XML code for the example that you are about to build is almost completely application agnostic; it will work equally well in Excel and Word, and it only requires a very minor change to work in Access. We’ll go through examples for each application so that you will be comfortable and confident in all three.
N OT E The completed example files (comboBox-Fonts.xlsx, comboBox-Fonts .docx, and comboBox-Fonts.accdb) can be downloaded from the book’s website
at www.wiley.com/go/ribbonx.
Because we are only using built-in controls, this example does not require VBA. We’ll begin with Excel, so start by creating and saving a new xlsx file. Open the file in the CustomUI Editor, apply the RibbonBase template you created in Chapter 2, and insert the following code between the and tags:
Remember to validate your code to catch any pesky typing errors. Once you have done this, save the file, and open it in Excel. Navigate to the Demo tab and you will see the UI shown in Figure 7-3.
Figure 7-3: The Font and FontSize comboBox controls on a new Excel tab
91118c07.qxd:WileyRedTight
234
Part I
■
11/28/07
9:16 PM
Page 234
The Building Blocks for a Successful Customization
With regard to differences between Excel and Word, this code is 100% application agnostic. Try it for yourself — create a new .docx file and open it in the CustomUI Editor. Enter the same XML code and save the file. (Remember to validate the code if you retyped it all by hand!) Upon opening Word, you’ll see the Ribbon as it appears in Figure 7-4.
Figure 7-4: The Font and FontSize comboBox controls on a new Word tab
As we mentioned, there is a minor change required to get this code to work in Access. You merely need to revise the insertBeforeMso attribute, as TabHome is not a defined tab in Access. In other words, the Demo tab’s insertBeforeMso attribute for Access should read as follows: insertBeforeMso=”TabHomeAccess”
With this code in place, when you reopen the database, the UI will appear as shown in Figure 7-5.
Figure 7-5: The Font and FontSize comboBox controls on a new Access tab
“But wait,” you say, “that does not look exactly the same!” That’s true — the name of the font is not displayed because nothing is open that has a font that can be edited. If you open a table or a form, you’ll find that these controls are immediately enabled. Our example was intended to provide the added benefit of demonstrating when you might see the blank list, so you needn’t be alarmed that your code might have failed.
Creating Custom Controls Demonstrating how to use Font and FontSize comboBoxes as was an easy example that you will reuse many times, but we can only get so far using Microsoft’s built-in comboBoxes. It’s time to look at some examples of building items that do things that Microsoft hasn’t allowed for.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 235
Chapter 7
■
comboBox and dropDown Controls 235
The Excel and Word examples in this section display how to employ static lists in the comboBox controls and incorporate the item controls that were covered in the first section of this chapter. With the Access example, we start to explore a dynamic comboBox
control.
An Excel Example For this example, we again assume that you have hidden the entire user interface. In addition, also imagine that in an attempt to make your application look less like Excel, you have hidden all the worksheet tabs. You still want users to be able to move between the three worksheets, however, so you need to provide some vehicle to accomplish this. In many ways, the comboBox can be an ideal control for this kind of navigation: It lists all the “pages” in your application, and allows users to type in a specific one that they may wish to jump to. Naturally, you need a macro or two to make this work, so create a new macro-enabled Excel file (.xlsm). Save it, and then open it in the CustomUI Editor. Apply the RibbonBase template, and insert the following code between the and tags:
Notice that the comboBox makes use of the onChange callback to take action when an item is selected. In addition, this comboBox holds three items: Sheet1, Sheet2, and Sheet3. These items are static and cannot be changed from within the file; nor will the user be able to add additional items. This is a perfect fit for the goal of ensuring that users are able to navigate only to these worksheets. Before you close the CustomUI Editor, be sure to validate your code for typing errors, and then copy the onChange callback.
C R O S S-R E F E R E N C E For a refresher on working with callbacks and the CustomUI Editor, see Chapter 5.
91118c07.qxd:WileyRedTight
236
Part I
■
11/28/07
9:16 PM
Page 236
The Building Blocks for a Successful Customization
When you are back in Excel, open the VBE and paste the callback into a new standard module. Now you need to edit the callback to make it react as you wish. You can figure out how to do that by thinking through the order of events: 1. The user will select an item from the Worksheet list. 2. The callback will be triggered. 3. The selected value of the comboBox (in this case, the worksheet name) will be passed to the routine. 4. The worksheet will be activated. So far so good, but there is one more piece that may cause a glitch: We stated that users can only select one of the sheets specified in the code. And what happens if they type in a different value? To deal with these eventualities, you should edit the callback signature to read as follows: ‘Callback for rxcboSelectSheet onChange Sub rxcboSelectSheet_Click(control As IRibbonControl, text As String) On Error Resume Next Worksheets(text).Activate If Err.Number 0 Then MsgBox “Sorry, that worksheet does not exist!” End If End Sub
The second line of code attempts to activate the worksheet that has been passed to the callback. Items chosen from the list will always be a valid name, but text typed in by the user may not match an item in the list. The On Error statement at the beginning of the routine deals with this by telling the code to continue to the next line even if an error is present. At this point, you can check whether the Err property is zero. If the Err property does not equal zero, then an error must have occurred; therefore, the value the user typed is not valid. In addition, because you want the user to know that their input wasn’t acceptable, you include a message box. Of course, you can display whatever message that you’d like and even add other options, but message box customization is outside our focus, so let’s keep moving. Now that your callback is set up correctly, click the Navigation tab and play with the comboBox. Try selecting items from the list, as well as typing in a value, as shown in Figure 7-6.
Figure 7-6: Using a comboBox to change worksheets in Excel
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 237
Chapter 7
■
comboBox and dropDown Controls 237
A Word Example Recall that in Chapter 6 we built a customization that allowed the user to enter the width of the Styles pane in an editBox. Although this works nicely for giving users the capability to control the width, it isn’t very intuitive, as there is no indication of what type of values to put in the box. There are some techniques that provide this information to the user, but those are more complex, and we cover them in Chapter 11. For now, you can use the comboBox to provide the user with a pre-set list of options from which to choose. To make things easy for the user, we keep the checkBox that we used to toggle the Styles pane on and off. In addition, rather than create everything from scratch, you can use the editBox example file created in Chapter 6. In that, we replaced the editBox with the comboBox control.
N OT E Instead of using the editBox example you created in the previous chapter, you can also download the complete editBox example, editBox-Style Inspector Width.docm, from the book’s website.
To get started, open the existing file in the CustomUI Editor and replace the editBoxspecific XML with the following:
As usual, validate the XML code before saving the file, and because you already have the checkBox callbacks programmed in the file, you only need to copy the callback signature for the rxcboStyleWidth_click event. If you examine this XML, you will see that we have added eight options to the comboBox’s drop-down list portion: the numbers 1.00 through 8.00. Remember that while each item’s id must be unique, it is the item’s label that will be passed to the callback when the control is accessed.
91118c07.qxd:WileyRedTight
238
Part I
■
11/28/07
9:16 PM
Page 238
The Building Blocks for a Successful Customization
T I P In this example, a four-character sizeString is declared even though we only show three digits. This is because we want to allow a number with two decimal places and one leading digit. The decimal also counts as a character, so we must include it in our maximum size.
Upon opening Word, you will again want to jump into the VBE right away to paste your callback. Recall that earlier in the chapter we mentioned that the comboBox control is a hybrid between the editBox and dropDown controls. This works very much to your benefit in this case because the editBox callback already exists in the file. To port the code from the editBox to the comboBox, you have two options: 1. Copy the code from the rxtxtStyleWidth_getText routine to the rxcboStyleWidth_Click routine and delete the original rxtxt routine. 2. Replace the rxtxtStyleWidth_getText signature line with the rxcboStyleWidth_Click line.
T I P If you elect to rewrite the signature line, it is a good idea to get into the practice of making sure that you rewrite all of the parameters as well, not just the name of the callback. This will ensure that if you are updating from one type of callback to another it will still run.
T I P If you paste a callback signature line into your module and then update another callback to an identical signature, be sure to delete or rename the original. Otherwise, you will receive an error that an “Ambiguous Name Has Been Detected.” Once you have updated your procedure, save the file and select the View tab. Your updated Style Options will look like what is shown in Figure 7-7.
Figure 7-7: Using a comboBox to change Word’s Style Inspector width
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 239
Chapter 7
■
comboBox and dropDown Controls 239
Try using the comboBox to change the width of the Style Inspector pane. Notice the following: ■■
It only shows when the checkbox is checked.
■■
The width changes each time you select a new value.
■■
Typing a custom value also changes the Style Inspector width.
■■
Typing text will return a custom error message.
An Access Example In this example, we again build on the Access database used in the last chapter. This time, we add a new group containing a comboBox that opens a form directly to a specific field. Unlike previous examples that used static callbacks, this customization makes use of callbacks to dynamically populate the comboBox with a list of all the authors in the tblAuthors table. To do this, we repopulate the comboBox every time the form is closed. We recognize that the data in the comboBox and the table may become out of sync, but it could be impractical to maintain the XML code for each and every record added to the database, as frequent updates would have a significant performance impact. To get started, open the Access example from the previous section or download the toggleButton-Form Tools.accdb file from the book’s website. Copy the RibbonX information from the RibbonXML field of the USysRibbons table and paste it into a fresh instance of the CustomUI Editor. Between the and tags, enter the following XML for the comboBox control:
As you can immediately see, the comboBox specification looks much different than that shown in the Excel and Word examples. A host of callbacks are declared and no item controls are listed. Because the items will be provided dynamically by the callbacks at run-time, it isn’t necessary to declare any static items.
N OT E Although it has been mentioned before, it is worth repeating a very important point: Even if you did want to declare one or more static item controls that will show up in every comboBox control you use, it must be done through the callback if you elect to populate any controls dynamically. Use of dynamic (or static) callbacks is an “all or nothing” approach.
91118c07.qxd:WileyRedTight
240
Part I
■
11/28/07
9:16 PM
Page 240
The Building Blocks for a Successful Customization
C R O S S-R E F E R E N C E Another thing to notice about the preceding XML code is that there is no tag. Instead, the comboBox declaration ends with a /> instead of the / character. As discussed in Chapter 3, this is possible because no child elements are declared for the comboBox control.
Once you have the code validated and copied into the RibbonXML field of the USysRibbons table, generate and copy all of the rxcboSearchAuthor callbacks. If you are keeping track, you will notice that we have declared four callbacks, but only three callouts are generated by the CustomUI Editor. Specifically, the rxcboSearchAuthor_getItemId callback signature is missing, which appears to be a bug in the CustomUI Editor program. Fortunately, you can consult Table 7-5, earlier in this chapter, and obtain the signature for this callback. It is declared as follows: Sub GetItemID(control As IRibbonControl, index As Integer, ByRef id)
N OT E If for some reason you don’t have this book with you when you’re attempting this in a project, you can also search for these callback signatures on the Internet. An article containing all of the callback signatures can be located on the MSDN site at the following URL: http://msdn2.microsoft.com/ en-us/library/ad7222523.aspx
Most of the callback signatures have been generated for you, so copy and paste them into the code module that holds the rest of the code. Next, you need to manually type in the GetItemID callback signature. Before we start writing the code, it’s time to figure out exactly how this works. Again, you do this by thinking through the steps in a logical manner. We want to do the following: 1. Create a list of all the authors currently in the database. 2. Submit that list to the comboBox. 3. Open the form when the user clicks on (or manually enters) an author’s name. 4. Let the user know when an author cannot be found (and close the form). 5. Start again at Step 1 when the form is closed (to ensure that the list is current if any authors were added). As with all code, the methods by which you accomplish these things are limited only to your imagination and ability. What is detailed below can certainly be done differently, but it works well for the purposes of this example. To begin, the code needs a place to store the list of author names, and a count of those names. To do this, you add two global variables to the top of your project, just underneath the Option lines. The first is a variant array (a dynamically sized space to hold the list of names), and the second is a Long data type, which can hold the count of the names in the array. They read as follows: Public gaAuthorList As Variant Public glngItemCount As Long
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 241
Chapter 7
■
comboBox and dropDown Controls 241
C R O S S-R E F E R E N C E For a review of variant arrays, please see Chapter 4. Now that you have a variable to hold the list of names, it makes sense to work on the routine that populates the list. Because we indicated that the list will be populated when the file is loaded and every time a form is closed, it makes sense to build the process into a routine, and to call the routine whenever we need to check the table and populate the list. The routine looks as follows: Sub PopulateAuthorArray() ‘Populate the global array of author names Dim db As DAO.Database Dim rst As DAO.Recordset Dim lngLoopCount As Long ‘Open the recordset for the authors table Set db = CurrentDb() Set rst = db.OpenRecordset(“tblAuthors”, dbOpenTable) ‘Store list of all authors in an array On Error Resume Next lngLoopCount = 0 With rst If .EOF Then .MoveFirst glngItemCount = .RecordCount ReDim gaAuthorList(glngItemCount - 1) Do While Not (.EOF) gaAuthorList(lngLoopCount) = !AuthorName.Value .MoveNext lngLoopCount = lngLoopCount + 1 Loop End With ‘Release all objects rst.Close db.Close Set rst = Nothing Set db = Nothing End Sub
The routine loops through all the records in the tlbAuthors table and adds them to the gaAuthorList array. In addition, it sets the glngItemCount variable to hold the total number of records that were in the table (and therefore the array.) Next, we deal with the callbacks. The getItemCount callback simply returns the number stored in the glngItemCount variable, telling the control how many records you have: ‘Callback for rxcboSearchAuthor getItemCount Sub rxcboSearchAuthor_getItemCount(control As IRibbonControl, _ ByRef returnedVal) returnedVal = glngItemCount End Sub
91118c07.qxd:WileyRedTight
242
Part I
■
11/28/07
9:16 PM
Page 242
The Building Blocks for a Successful Customization
The getItemID callback is used to create a unique item ID for each of the item controls that are dynamically fed into the comboBox. The index variable is always unique (numbered from zero to the number of items you have, less one), so you can simply add the index to a string of text, as shown here: ‘Callback for rxcboSearchAuthor getItemID Sub rxcboSearchAuthor_getItemID(control As IRibbonControl, _ index As Integer, ByRef ID) ID = “rxitemcboSearchAuthor” & index End Sub
T I P You may wonder why we didn’t just set up this callback using ID
= index
to generate an ID for the item. We could have used that approach, but it would fail if we added another comboBox to the project and used the same logic because the IDs would no longer be unique (i.e., two controls could end up with an id=0.) It is a far better practice to ensure that your ID is tagged with your control name. Therefore, in addition to generating a unique ID, you are now generating a coding habit that will never leave you debugging this issue.
The getItemLabel callback returns the author’s name from the global array that was established earlier. When the array is created, each element has an index, or place, in the array. You can use the following callback to return the element of the array corresponding to the index number: ‘Callback for rxcboSearchAuthor getItemLabel Sub rxcboSearchAuthor_getItemLabel(control As IRibbonControl, _ index As Integer, ByRef returnedVal) returnedVal = gaAuthorList(index) End Sub
N OT E If you are experienced with changing the base of an array and intend to apply that technique in these databases, you need to adjust these indexes to coincide with your arrays. The Click event is the final callback that needs programming in our example. It uses the following code: ‘Callback for rxcboSearchAuthor onChange Sub rxcboSearchAuthor_Click(control As IRibbonControl, text As String) ‘Open the form at the requested Author Dim sAuthorName As String Dim rs As DAO.Recordset ‘Open the appropriate form sAuthorName = “[AuthorName] = ‘“ & text & “‘“
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 243
Chapter 7
■
comboBox and dropDown Controls 243
DoCmd.OpenForm “frmAuthors” ‘Find the correct author With Forms!frmAuthors Set rs = .RecordsetClone rs.FindFirst sAuthorName If rs.NoMatch Then MsgBox “Author Not found” DoCmd.Close acForm, “frmAuthors”, acSaveNo Else .Bookmark = rs.Bookmark End If End With ‘Release the objects Set rs = Nothing End Sub
This event opens the form and attempts to activate the record pertaining to the author who has either been selected from the comboBox or typed in manually. If the record can be found it is “bookmarked” and activated. If the record is not found, the user is informed (via our friendly message box) and the form is closed. There is one final thing left to do in order for this example to work: You need to hook the PopulateAuthorArray to the appropriate routines. Without this hook, the array will never be filled and the comboBox will sit empty. To create the hook, simply insert the following code snippet in the rxIRibbonUI_ onLoad event (before or after the line that captures the RibbonUI), as well as in the frmAuthor’s Form_Deactivate event (just before the line that invalidates the RibbonUI): ‘Populate the array of Author names Call PopulateAuthorArray
Now that you have made all of these modifications, save, close, and reopen your database. You will have a new group on the Ribbon that holds the combBox. Select an author’s name from the list and you will be taken to their record, as shown in Figure 7-8.
Figure 7-8: Using a comboBox to jump to a specific
91118c07.qxd:WileyRedTight
244
Part I
■
11/28/07
9:16 PM
Page 244
The Building Blocks for a Successful Customization
record in a form
Now try adding your name to the Author’s table. After all, you have done a lot of work just to get to this point! After adding your name, close the form and check the comboBox list. Voilà, your name in print!
The dropDown Element Like the comboBox, the dropDown control presents the user with a pre-defined list of options from which to choose. In addition, it too can be populated either at design-time using XML to provide a static list, or dynamically at run-time via callbacks. The biggest of differences between the comboBox and dropDown controls lies in the ability of the comobBox to accept user-entered data; the dropDown control has no such facility, forcing the user to select an item from the pre-defined list and only from that list. At first glance, you may ask yourself why anyone would want to use a dropDown over a comboBox. After all, wouldn’t you always want to make the controls more robust and accessible? The answer depends upon your implementation, of course, but some reasons you may want to use the dropDown control include the following: ■■
You do not want users to enter their own information.
■■
Your list is not long, so using the “auto complete” capability is not a concern.
■■
You are not interested in programming the callbacks to validate user-entered data.
Required Attributes of the dropDown Element To create a dropDown control, you need to define one and only one of the id attributes shown in Table 7-6. Table 7-6: Required id Attributes for the dropDown Element ATTRIBUTE
WHEN TO USE
id
When creating your own dropDown
idMso
When using an existing Microsoft dropDown
idQ
When creating a dropDown shared between namespaces
Optional Static and Dynamic Attributes with Callback Signatures The dropDown element optionally accepts any one insert attribute shown in Table 7-7.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 245
Chapter 7
■
comboBox and dropDown Controls 245
Table 7-7: Optional insert Attributes for the dropDown Element INSERT ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
WHEN TO USE
insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control
insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
In addition to the insert attribute, you may also include any or all of the optional static attributes or dynamic equivalents shown in Table 7-8. Table 7-8: Optional Attributes and Callbacks of the dropDown Element VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED DEFAULT VALUES VALUE
enabled
getEnabled
true, false, true 1, 0
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
image
getImage
1 to 1024 (none) characters
Sub GetImage (control As IRibbonControl, ByRef returnedVal)
imageMso
getImage
1 to 1024 (none) characters
Same as above
(none)
getItemCount
1 to 1024 (none)
Sub GetItemCount (control As IRibbonControl, ByRef returnedVal)
(none)
getItemID
Unique (none) text string
Sub GetItemID (control As IRibbonControl, index As Integer, ByRef id)
(none)
getItemImage
1 to 1024 (none) characters
Sub GetItemImage (control As IRibbonControl, index As Integer, ByRef returnedVal) Continued
91118c07.qxd:WileyRedTight
246
Part I
■
11/28/07
9:16 PM
Page 246
The Building Blocks for a Successful Customization
Table 7-8 (continued) VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED DEFAULT VALUES VALUE
(none)
getItemLabel
1 to 1024 (none) characters
Sub GetItemLabel (control As IRibbonControl, index As Integer, ByRef returnedVal)
(none)
getItemScreentip
1 to 1024 (none) characters
Sub GetItemScreenTip (control As IRibbonControl, index As Integer, ByRef returnedVal)
(none)
getItemSupertip
1 to 1024 (none) characters
Sub GetItemSuperTip (control As IRibbonControl, index As Integer, ByRef returnedVal)
keytip
getKeytip
1 to 3 (none) characters
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal)
label
getLabel
1 to 1024 (none) characters
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
screentip
getScreentip
1 to 1024 (none) characters
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal)
(none)
getSelected ↵ ItemID
Unique (none) text string
Sub GetSelectedItemID (control As IRibbonControl, ByRef returnedVal)
(none)
getSelected ↵ ItemIndex
1 to 1024 (none)
Sub GetSelectedItemIndex (control As IRibbonControl, ByRef returnedVal)
showImage
getShowImage
true, false, true 1, 0
Sub GetShowImage (control As IRibbonControl, ByRef returnedVal)
showItem ↵ Image
(none)
true, false, true 1, 0
(none)
showItem ↵ Label
(none)
true, false, true 1, 0
(none)
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 247
Chapter 7
■
comboBox and dropDown Controls 247
Table 7-8 (continued) VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED DEFAULT VALUES VALUE
showLabel
getShowLabel
true, false, true 1, 0
Sub GetShowLabel (control As IRibbonControl, ByRef returnedVal)
sizeString
(none)
1 to 1024 12* characters
(none)
supertip
getSupertip
1 to 1024 (none) characters
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 1024 (none) characters
(none)
visible
getVisible
true, false, true 1, 0
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
(none)
onAction
1 to 1024 (none) characters
Sub OnAction (control As IRibbonControl, selectedId As String, selectedIndex As Integer)
N OT E The default value for the sizeString attribute (if the attribute is not declared at all) is approximately 12, but this will vary depending on the characters used and the system font.
Allowed Children Objects of the dropDown Element The only child object that can be used with the dropdown element is the item element.
Parent Objects of the dropDown Element The dropDown element may be used within the following controls: ■■
box
■■
group
91118c07.qxd:WileyRedTight
248
Part I
■
11/28/07
9:16 PM
Page 248
The Building Blocks for a Successful Customization
Graphical View of dropDown Attributes Figure 7-9 shows a dropDown control on a Ribbon group, which houses two dynamic items. The captions annotate the properties that can be set on a dropDown control.
Figure 7-9: Graphical view of the dropDown element
Using Built-in Controls Because there are no built-in dropDown controls that span all the applications, this example again focuses on referencing one of Excel’s native dropDown controls: the BorderStyle control. To make use of the control, create a new xlsx file and open it in the CustomUI Editor. Apply the RibbonBase template and place the following code between the and tags:
Validate your XML, save the file, and reopen it in Excel. You will now see the fully functional control available on the Demo tab, as shown in Figure 7-10.
Figure 7-10: Using the BorderStyle control in a custom Excel group
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 249
Chapter 7
■
comboBox and dropDown Controls 249
Creating Custom Controls In this section we again create custom tools, rather than reuse the commands and functionality inherent in the programs. In addition to employing a static dropDown list in Excel, the previous comboBox example will also be revised so that it dynamically populates a dropDown control. The Word example will remain static, but we add another useful tool to the collection that you have been building. The Access example again creates a completely dynamic list in the dropDown control.
An Excel Example This example is quite interesting because it uses two dropDown elements in tandem. Similar to the comboBox example in the previous section, the first control lists all the worksheets in the workbook. In this case, however, we employ the available callbacks to update the dropDown list as worksheets are added to or removed from the workbook. Pretty cool — and you can certainly see the value in the example and anticipate ample opportunities to incorporate this into your customizations.
N OT E By switching from a comboBox to a dropDown, we lose the capability to type in the worksheet we want to activate. It should be assumed that this process was a conscious choice on the part of the developer.
The second control allows us to toggle the visibility of the selected worksheet between Excel’s three states: xlSheetVisible, xlSheetHidden, and xlSheetVeryHidden.
N OT E The xlSheetVeryHidden state of an Excel worksheet is not known to most users because it must be set from VBA. This is a state that enables a developer to completely hide a worksheet from the users, and it will not show in the menu that allows users to unhide sheets.
One of the best ways to highlight the differences between the two controls is to convert one to the other, so that is exactly how this example begins. If you have not completed the previous example, or are unsure how it compares to the examples in this book, download the comboBox-Select Sheet.xlsm file from the book’s website. Before you open the file in Excel, open it in the CustomUI Editor and replace everything from and including the to the end of and including with the following XML:
91118c07.qxd:WileyRedTight
250
Part I
■
11/28/07
9:16 PM
Page 250
The Building Blocks for a Successful Customization
Notice a few things about the preceding code: ■■
The first dropDown declaration has no ending tag. This is because it contains all of its attributes within the code block and is requesting all of its child objects via callbacks; therefore, it does not require any static child objects declared in the XML, and can be closed by using /> at the end.
■■
The callback signature for the dropDown is different from the comboBox. Whereas the comboBox used an onChange callback, the dropDown uses an onAction callback when it is clicked.
■■
The second dropDown list does have a tag to close it. This is because it holds a static list of item objects declared directly in the XML code.
In addition, we also need to capture the RibbonUI object in order to update the lists dynamically. Adjust the CustomUI tag to include the onLoad element as shown below:
As always, you should validate your code, save it, and generate and copy the callback signatures. As with the comboBox element, be aware that the callback for the getItemID callback will not be generated by the CustomUI Editor. If you were doing this on your own, you’d once again need to look this up and type it in manually (or copy it from a functional example). Open Excel again, but don’t be alarmed when you see the error message indicating that it cannot run the macro, shown in Figure 7-11.
Figure 7-11: Error message indicating a missing callback
This is to be expected, as we have declared an onLoad callback but have not yet provided the programming. To do that, in the VBE, open the code module that holds all the RibbonX event code and paste the callback signatures at the end.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 251
Chapter 7
■
comboBox and dropDown Controls 251
Before we go any further, let’s deal with the onLoad callback. You want to add two variables to the top of the project. The first will hold the RibbonUI object, while the second will store the worksheet name that was selected. They are placed just under the Option Explicit line and should be declared as follows: Public RibbonUI As IRibbonUI Dim sSheetName As String
Next, we make sure that the RibbonUI object is captured at load time by setting the onLoad callback as follows: ‘Callback for customUI.onLoad Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set RibbonUI = ribbon End Sub
Now it’s time to look at the rest of the callback signatures, starting with the callback that tells the control how many items exist. Fortunately, it doesn’t require much code to make this work, as the item count will always be equal to the number of worksheets in the workbook. You can use the following code: ‘Callback for rxddSelectSheet getItemCount Sub rxitemddSelectSheet_getItemCount(control As IRibbonControl, _ ByRef returnedVal) returnedVal = Worksheets.Count End Sub
Next, we set up the callback for the getItemLabel, which returns the text of each item to the dropDown list: ‘Callback for rxddSelectSheet getItemLabel Sub rxitemddSelectSheet_getItemLabel(control As IRibbonControl, _ index As Integer, ByRef returnedVal) returnedVal = Worksheets(index + 1).Name End Sub
If you haven’t worked much with arrays, you might not notice a very big issue lurking in the middle of this routine. Pay careful attention to the fact that the name being returned for an item is the index + 1. The reason for this shift is that by default VBA works in zero-based arrays (as previously mentioned, VBA starts counting at 0, not 1), but Excel’s default worksheet indexes and names work in one-based arrays (Excel starts counting at 1, not 0.) To understand the ramifications, assume that we have a workbook set up to start with the default 3 worksheets. In the getItemCount procedure, we asked for a count of the Worksheets 1, 2, and 3. We received a total count of 3, as we would expect. What is interesting, however, is how this actually dimensions the array. Have a look at Table 7-9 to see how the array will manifest itself.
91118c07.qxd:WileyRedTight
252
Part I
■
11/28/07
9:16 PM
Page 252
The Building Blocks for a Successful Customization
Table 7-9: Dimensions and Values of an Array ARRAY INDEX
ELEMENT VALUE
ACTUAL VALUE
0
Worksheets(index + 1).Name
Sheet1
1
Worksheets(index + 1).Name
Sheet2
2
Worksheets(index + 1).Name
Sheet3
N OT E If this appears intimidating, don’t worry about it. It is confusing for most users who are not accustomed to working with arrays. As a rule of thumb, if the values you are trying to pass in to or retrieve from an array appear to be out of sync by one, just adjust your index, as shown in the previous code snippet. Next, you will want to ensure that the callback is programmed to dynamically generate the unique ID for each of the drop-down items. The callback should read as follows: ‘Callback for rxddSelectSheet getItemID Sub rxitemddSelectSheet_getItemID(control As IRibbonControl, _ index As Integer, ByRef id) id = “rxitemddSelectSheet” & index End Sub
There is one more callback to set up: the rxddSelectSheet_Click callback. Because we started with the previous file, you have all the code for the rxcboSelectSheet_ Click event that was triggered by selecting a comboBox item. However, you can’t just rename and reuse. Take a look at Table 7-10, noting the difference between the callback signatures. Table 7-10: Difference between dropDown and comboBox Callback Signatures CONTROL
CALLBACK SIGNATURE
comboBox
rxcboSelectSheet_Click (control As IRibbonControl, text As String)
dropDown
rxddSelectSheet_Click (control As IRibbonControl, id As String, ↵ index As Integer)
You can see that while the comboBox passes the text of the control to the callback, the dropDown is not quite so friendly. Instead, it passes the ID and the index number. Unfortunately, we’re not interested in that number at this time; we need to show the user the actual name of the sheet. In order to work out the control’s name, we can leverage one of the less obvious features of Ribbon construction. We can actually call the getItemLabel callback to give us this information!
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 253
Chapter 7
■
comboBox and dropDown Controls 253
If you take a good look at the getItemLabel callback, you’ll see that it accepts three parameters. The key to making this work is the keyword that prefaces the variable in the last parameter: Sub rxitemddSelectSheet_getItemLabel(control As IRibbonControl, _ index As Integer, ByRef returnedVal)
In VBA, every parameter is passed to a procedure specifying that the variable is passed by either ByVal (which is the default, so the parameter is typically omitted, as it is implied) or ByRef. The difference is that when a variable is passed ByVal, a copy of the variable is used inside the receiving procedure. Anything that is done to it within the procedure is lost when the procedure goes out of scope (ends). In contrast, when a variable is passed to a procedure ByRef, the actual variable is passed as a parameter. Anything that is done to the variable inside that procedure is passed back to the calling procedure when the called procedure ends. It’s this capability that enables us to make use of the getItemLabel callback. Update your callbacks so that the rxddSelectSheet_Click routine reads as follows, and delete the rxcboSelectSheet_Click event: ‘Callback for rxddSelectSheet onAction Sub rxddSelectSheet_Click(control As IRibbonControl, id As String, _ Index As Integer) On Error Resume Next Call rxitemddSelectSheet_getItemLabel(control, index, sSheetName) If Err.Number 0 Then MsgBox “Sorry, that worksheet does not exist!” RibbonUI.InvalidateControl “rxddSelectSheet” End If End Sub
Make note of how we are calling the getItemLabel callback, and especially how we are passing the sSheetName variable to the returnedVal parameter. Because the actual sSheetName variable is passed, and not a copy, the changes made in that procedure will replicate back to the global variable, and the worksheet name will be ready when we need it. Finally, we’ve completed the setup for the dynamic dropDown control, and we can focus on setting up the sole callback for the static dropDown. Update the callback for this control to read as follows: ‘Callback for rxddSheetVisible onAction Sub rxddSheetVisible_Click(control As IRibbonControl, id As String, _ index As Integer) `Check that a worksheet has been selected On Error Resume Next sSheetName = Worksheets(sSheetName).Name If Err.Number 0 Then MsgBox “Sorry, but you need to select a valid sheet first!” Exit Sub End If
91118c07.qxd:WileyRedTight
254
Part I
■
11/28/07
9:16 PM
Page 254
The Building Blocks for a Successful Customization
‘Change the sheet’s visibility Select Case id Case “rxitemddSheetVisible1” Worksheets(sSheetName).Visible = xlSheetVisible Case “rxitemddSheetVisible2” Worksheets(sSheetName).Visible = xlSheetHidden Case “rxitemddSheetVisible3” Worksheets(sSheetName).Visible = xlSheetVeryHidden End Select ‘Tell user if it is last visible sheet If Err.Number 0 Then MsgBox “Sorry, this is the only visible sheet.” & vbCrLf & _ “You can’t hide them all!” End If On Error GoTo 0 End Sub
This callback evaluates whether any value is contained in the sSheetName variable. If there is one, it then sets the visibility of the sheet as chosen by the user. You’ll recall that we experienced an error when the file was originally loaded, so we won’t be able to try out our customization until we save and reload the workbook. Once you’ve done this, navigate to the Review tab and play with the new controls, shown in Figure 7-12.
Figure 7-12: The sheet visibility dropDown menu set
A Word Example In the tradition of adding useful tools to the Word Ribbon groups, we’re going to continue adding another feature to the example that we created in the section on the comboBox. If you frequently use Word templates, you may find that you are offered a ton of styles that you don’t use, which results in a lot of unwanted clutter. Word has four distinct settings to filter the styles to make them easier to use. In this section, we’ll create a customization that provides the capability to quickly select these four settings. To start, make a copy of the Word comboBox example from the previous section, or download the completed comboBox-Style Inspector Width.docm file from the book’s
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 255
Chapter 7
■
comboBox and dropDown Controls 255
website. Open the new file in the CustomUI Editor and insert the following XML between the and tags:
This will enter a static dropDown under the existing controls. It holds five different items: four related to the Styles view options and a final option to hide the Styles task pane. Validate the code, as you usually do, and copy the rxddStylesView_Click callback. Open Word, open the VBE, and paste your callback signature in the module that holds the rest of the existing callback signatures. The next step is to record a macro that will capture as much information about the process as possible.
T I P Never overlook the macro recorder, as it is a handy tool. Even if it can only record pieces of the process, the documentation provides a great place to start learning about the objects that you are trying to manipulate. In addition, you can save a lot of time and avoid typos by copying and pasting from the generated code. We’re trying to capture a couple of different things here. The first is how to show and hide the Styles task pane at the side of the document. The second is how to select the desired filtered view. Let’s walk through the process. To begin, start the macro recorder and press Alt+Ctrl+Shift+S to show the Styles task pane on the side of your screen. (You could also navigate to the Home tab and click the little arrow in the bottom, right corner of the Styles group, if you prefer.) Next, in the bottom right corner of the Styles pane, you will see an Options link. Click the link and you will see the dialog shown in Figure 7-13. In the drop-down list, select Recommended and then click OK. Return to the Style Pane Options and select each option in turn, saying OK to each one. Finally, close the Styles task pane.
91118c07.qxd:WileyRedTight
256
Part I
■
11/28/07
9:16 PM
Page 256
The Building Blocks for a Successful Customization
Figure 7-13: The Style Pane Options dialog
Now it’s time to examine the code that was recorded. You should see something similar to the annotated version shown here: Sub Macro1() ‘All styles ActiveDocument.FormattingShowFilter = ‘In current document Styles ActiveDocument.FormattingShowFilter = ‘In use Styles ActiveDocument.FormattingShowFilter = ‘Recommended Styles ActiveDocument.FormattingShowFilter = ‘Turn off the Styles task pane CommandBars(“Styles”).Visible = False End Sub
wdShowFilterFormattingInUse wdShowFilterStylesAll wdShowFilterStylesInUse wdShowFilterStylesAvailable
N OT E We’ve annotated the code because the constants that Microsoft used are less than intuitive. One might think that the wdShowFilterStylesAll would apply the All setting, not the In Current Document setting, as it does. Rather than have you waste time figuring out which is which, we’ve provided the clarification so that you can stay focused on the exercise.
N OT E There is no code recorded to launch the Styles pane, but there is code to close it. Interestingly enough, once you have opened the Styles pane in your current Word instance, you can show the Styles task pane by executing the command CommandBars(“Styles”).Visible = true. Until you have activated it at least once manually, however, the code will fail. This is a known bug in Microsoft Word, and it is hoped that it will be fixed in an upcoming service pack. There is a workaround for the issue, however, which is to replace CommandBars(“Styles”).Visible = true with Application .TaskPanes(wdTaskPaneFormatting).Visible = True.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 257
Chapter 7
■
comboBox and dropDown Controls 257
The good news here is that you can easily adapt this code into the callback signature, as the commands are relatively straightforward. Placing a case statement in the callback will give us the following: ‘Callback for rxddStylesView onAction Sub rxddStylesView_Click(control As IRibbonControl, id As String, _ Index As Integer) On Error Resume Next Application.TaskPanes(wdTaskPaneFormatting).Visible = True Select Case id Case “rxitemddStylesAll” ActiveDocument.FormattingShowFilter = wdShowFilterFormattingInUse Case “rxitemddStylesInCurrent” ActiveDocument.FormattingShowFilter = wdShowFilterStylesAll Case “rxitemddStylesInUse” ActiveDocument.FormattingShowFilter = wdShowFilterStylesInUse Case “rxitemddStylesRecommended” ActiveDocument.FormattingShowFilter = wdShowFilterStylesAvailable Case “rxitemddStylesNone” CommandBars(“Styles”).Visible = False End Select End Sub
_
_
_
_
N OT E The addition of the line continuations (a space followed by the underscore and a hard return) is not essential to making the code work, but it does make it easier to read. Once you have this code fully integrated, save the file and browse to the View tab. Try using various selections on the Show Styles dropDown, as shown in Figure 7-14.
Figure 7-14: The Show Styles dropDown in use
91118c07.qxd:WileyRedTight
258
Part I
■
11/28/07
9:16 PM
Page 258
The Building Blocks for a Successful Customization
An Access Example The final example in this section also builds on the previous file. This time, you replace the button that was created to launch the Authors form with a dynamic dropDown list that will launch any of the forms. This dropDown will update each time a new form is added to, or removed from, the database. As an added twist, we also add a callback to retrieve an image for each item in the dropDown list. Make a copy of the Access example from the previous section or download the comboBox-Author Query.accdb file from the book’s website. Open it and copy the RibbonX information from the RibbonXML field of the USysRibbons table. Once you’ve done that, open the CustomUI Editor and paste the code in the blank pane. Locate the following code:
Replace it with this code:
Like the comboBox code used in the previous example, setting up a dynamic dropDown involves a great many callbacks. As always, validate the XML code. Once it is error-free, copy all of the XML and replace the code that is stored in the RibbonX field of your USysRibbons table. Next, use the CustomUI Editor to generate the callbacks, copy all of the rxddSelectForm signatures, and paste them to the code module that holds the rest of the callback code. Be sure to manually create the following callback that the CustomUI Editor does not generate: ‘Callback for rxddSelectFrom getItemID Sub rxddSelectForm_getItemID(control As IRibbonControl, _ index As Integer, ByRef ID) End Sub
Now that all of the callbacks are placed in the module, it’s time to start hooking them up to do the things that we want. By examining the XML code, you can see that the dropDown control will be placed in the group with an icon beside it. The rest of the content, of course, will be dynamic.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 259
Chapter 7
■
comboBox and dropDown Controls 259
In order to populate the dropDown list dynamically, you need to return the total number of items that the list will contain, which is done using the getItemCount callback. We’re interested in knowing the total number of forms in the database, and that number can be retrieved with the following code: ‘Callback for rxddSelectForm getItemCount Sub rxddSelectForm_getItemCount(control As IRibbonControl, _ ByRef returnedVal) returnedVal = CurrentProject.AllForms.Count End Sub
You also know that each item in the dropDown list must have its own unique ID. As in the previous examples, this can easily be accomplished by setting up the callback to read as follows: ‘Callback for rxddSelectFrom getItemID Sub rxddSelectForm_getItemID(control As IRibbonControl, _ index As Integer, ByRef ID) ID = “rxitemddSelectForm” & index End Sub
Next, you need to determine how to get the label for each item. Because each form has an index number in the collection, the callback can be set up as follows: ‘Callback for rxddSelectForm getItemLabel Sub rxddSelectForm_getItemLabel(control As IRibbonControl, _ index As Integer, ByRef returnedVal) returnedVal = CurrentProject.AllForms(index).Name End Sub
One more callback needs to be set up in order to populate the items in the list box. The XML code specifies that we also want an image for each item in the list, so we need a callback to get the image. The completed callback signature will look like this: ‘Callback for rxddSelectForm getItemImage Sub rxddSelectForm_getItemImage(control As IRibbonControl, _ index As Integer, ByRef returnedVal) Select Case CurrentProject.AllForms(index).Name Case Is = “frmAuthors” returnedVal = “FileCreateDocumentWorkspace” Case Else returnedVal = “HappyFace” End Select End Sub
This callback looks up the name of the form by passing the index number to the AllForms collection. Once that name has been returned, it is evaluated by the case
91118c07.qxd:WileyRedTight
260
Part I
■
11/28/07
9:16 PM
Page 260
The Building Blocks for a Successful Customization
statement. If the form name matches any of the cases, it is assigned the picture specified. The Else portion of this code assigns Microsoft’s HappyFace image to any form that is not specified in the code.
N OT E Remember that the contents of the dropDown list will be populated dynamically. This is important, as a user may add a new form — so it would not likely be in the list. By using the Else statement to assign a default image, you don’t have to worry about constantly updating the code. Now that all the callbacks are in place to populate the dropDown, we need to add the callback to handle the user’s selections. We’ll use the onAction callback, which is set up as shown here: ‘Callback for rxddSelectForm onAction Sub rxddSelectForm_click(control As IRibbonControl, ID As String, _ Index As Integer) Dim sFormName As String Call rxddSelectForm_getItemLabel(control, index, sFormName) DoCmd.OpenForm sFormName, acNormal RibbonUI.InvalidateControl (“rxddSelectForm”) End Sub
Note that this routine again uses the trick of retrieving the item’s label by leveraging the ability to set the sFormName variable by executing the getItemLabel callback. Once the selected form name has been retrieved, the form is opened. The invalidation contained in this routine ensures that each time a form is opened, the dropDown is repopulated. Therefore, if a new form has been added, then it will be added to and appear in the dropDown list.
N OT E Because a form needs to be opened via the dropDown to repopulate it, there is a one-click delay in the updating of the list each time a form is added to or deleted from the project. Ideally, the invalidation would actually be housed in a class module to monitor the creation and deletion of a form. However, this is more complicated and would distract from our demonstration of incorporating and using the dropDown control. We’ve placed the invalidation in the onAction callback. We encourage you to investigate creating a class module to monitor the form creation and removal events at your leisure. The final piece of programming work is to clean up remnant code from the button you replaced. All you need to do is locate the rxbtnFrmAuthors_click routine and delete it from the project. Now that all the code is in place, close and reload the Access database, thereby saving your work and allowing the new Ribbon to be installed as the file opens. When you return to your database, check the dropDown menu. Next, try adding a new form. Click on the menu to launch the Authors form and then check the dropDown
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 261
Chapter 7
■
comboBox and dropDown Controls 261
list again. You will see that your control now also houses the new form as well, as shown in Figure 7-15. This demonstrates that one-click delay just mentioned. The drop-down list needs to be refreshed after it is repopulated.
Figure 7-15: Dynamic dropDown list to select a form in Access
Conclusion As you’ve seen, the comboBox and dropDown controls are extremely similar in implementation and appearance. The examples presented in this chapter covered using static items defined in the XML code, as well as leveraging VBA to provide dynamically updated controls. There is no question, however, that one of the biggest deterrents to using either of these methods to set up an effective, fully dynamic solution is the copious amount of code that would have to be manually generated. When considering these controls, the main questions facing you are which one should you use, and when? The answer depends completely on how much latitude you wish to give your users. If, on the one hand, you need to limit users to selecting from a standard list that never changes, then you’ll want to use a dropDown control. On the other hand, if you want to offer your users either of the following options, then you will want to use a comboBox: ■■
The ability to jump to an item in the list
■■
The ability to enter text that is not already defined in the list
Now that you have learned how to present users with different lists of items, it is time to learn about two controls that can add impressive richness to your applications. In Chapter 8, you will learn how to incorporate the picture and gallery controls into your customizations.
91118c07.qxd:WileyRedTight
11/28/07
9:16 PM
Page 262
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 263
CHAPTER
8 Custom Pictures and Galleries
As you become more experienced in developing a custom UI, you will probably find that the built-in icon gallery just isn’t enough. You may want to take it to the next level and add a greater personal touch by introducing your own images (icon or pictures). In this chapter, you learn how to harness the power of custom pictures and how to use galleries, one of the coolest new features in the UI. As you are preparing to work through the examples, we encourage you to download the companion files. The source code and files for this chapter can be found on the book’s web site at www.wiley.com/go/ribbonx.
Custom Pictures Many of the examples you’ve seen so far involve built-in pictures (or images if you prefer). Using custom pictures is a highly visible way to truly personalize your UI. There are different ways you can add pictures to your project. You can attach them to the project itself or you can load them from external picture files. You will learn these methods as well as the unique way in which Access can work with pictures. First, however, you need to get acquainted with which formats are appropriate for the job.
Suggested Picture Formats If you really get into planning a customized UI, you will probably also consider using your own images to add that extra touch.
263
91118c08.qxd:WileyRedTight
264
Part I
■
11/28/07
9:16 PM
Page 264
The Building Blocks for a Successful Customization
Adding custom images is not rocket science, but it is important that you get to know some file formats so that you can make an educated choice as to which type you should use in particular situations. Consider, for example, Figure 8-1, which shows a customized button.
Figure 8-1: A custom image placed in a custom button to match the color scheme
The customization looks perfect because it uses an old trick that many Office powerusers and programmers have long used: the image background matches the UI background. For the most part, this approach is acceptable, with one significant caveat. It can be a problem if the color scheme (or color theme, if you prefer) is changed, as illustrated in Figure 8-2.
Figure 8-2: Changing color scheme disrupts the harmony of the UI
Now you can clearly see that the image no longer matches the background, and the framing makes your UI look plain ugly. By using the right file format for your pictures, you can avoid this problem. A good format for working with custom pictures is the PNG format. PNG files allow for full transparency (something not supported by some of the other possible formats such as BMP, GIF, and JPEG), which means you should not find yourself suffering the embarrassment caused by the scenario shown in Figure 8-2 after deploying your solution. To demonstrate this, we’ll save the image used for the Custom Button as a PNG file type and use the PNG file with two color schemes, shown in Figure 8-3.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 265
Chapter 8
■
Custom Pictures and Galleries 265
Figure 8-3: Full transparency solves the problem of differing UI color schemes.
As you can see from the composite image shown in Figure 8-3, by adding full background transparency to a PNG file you no longer need to worry about the user choosing different color schemes and destroying your cherished UI. However, we don’t live in a perfect world and, like everything else, PNG files can let you down in some circumstances. For example, if you need to load an image on-the-fly using the VBA LoadPicture function, you cannot use the PNG format. This limited scope of usage means that you will need to look for alternatives — either a different format or a different method to load the image.
T I P Check the two Excel files that accompany this chapter on the book’s website to contrast the differences. Change the color scheme and note the differences between a PNG file and a BMP file. You can use the same example for Access and Word. Table 8-1 shows some of the file formats you can use in your custom UI. Table 8-1: Some Possible Picture Formats for the Custom UI PICTURE EXTENSION
FORMAT
PNG
Portable Network Graphics is an excellent format to use with your custom UI if you plan to have it attached with the project. You cannot use the LoadPicture function with this file format.
BMP
This refers to a bitmapped picture format. It is useful for loading pictures using the LoadPicture function.
ICO
Refers to icon format
WMF
Refers to Windows Metafile. Also useful when loading using the LoadPicture function.
JPG, JPEG
JPEG (Joint Photographic Experts Group) format offers a lower quality for the image.
GIF
Graphic Interchange Format, like JPEG, offers lower image quality overall.
91118c08.qxd:WileyRedTight
266
Part I
■
11/28/07
9:16 PM
Page 266
The Building Blocks for a Successful Customization
If your UI will be custom-picture intensive, you should choose PNG over the other file formats. PNG is the only format that provides full support for transparency, and PNG pictures are highly compressible without loss of quality. In contrast, JPG and GIF formats are generally not recommended because of their low image quality.
N OT E If you are wondering whether PNG format is the Eighth Wonder, that question does not have a straight answer. Although the format is great, you cannot load it on-the-fly using the available tools in VBA. Instead, you need to get specialized help from the GDI+ subsystem. We explain more about this in the section “Using GDI+ to load PNG files” later in this chapter.
Appropriate Picture Size and Scaling Now that you know what types you can use in your custom UI, it is only appropriate that you invest some time in learning a few things about size and scaling. These factors are important to displaying clear and crisp pictures in your UI, rather than something sloppy and distorted. As a rule of thumb, figures should be sized at 16 × 16 and 32 × 32 with 96dpi, as shown in Figure 8-4. As you can see, these are square images. Although it isn’t imperative, square images minimize distortion.
Figure 8-4: 16 × 16 and 32 × 32 images are the perfect size.
The thing to keep in mind here is that you may have a picture file that has the right canvas dimensions (16 × 16 or 32 × 32) but whose picture is not in the correct dimension, meaning that the picture covers an area that is smaller than the total canvas size. This may cause distortion (or some other unexpected result) when it is added to the UI. A disparity between the image’s size and its canvas size often manifests as a white band representing the background of the image. You can learn more about the image dimensions and the canvas size by opening the image in an imaging software package, where you can look at the image (as a layer) against its background.
Adding Custom Pictures to Excel or Word Projects Because Excel and Word work differently from Access, we will present the methods separately. In this first section, we demonstrate how to get the job done in Excel and Word. We later work with Access to load images using different methods. You can load your custom pictures in different ways. You can attach the pictures in advance within the zipped XML group or you can load them on-the-fly. In this section, you will learn both methods.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 267
Chapter 8
■
Custom Pictures and Galleries 267
Using the Custom UI Editor When it comes to loading images, you will find that the methods used can vary from one another. You could, for example, go through the trouble of manually packaging them all together by sorting out the UI relationship files yourself. You’d soon learn that this involves too much unnecessary work and is prone to errors. An alternative is to simply use the Custom UI Editor and let it sort all the relationship files and attachments for you. That’s the approach that we’re going to use here.
C R O S S-R E F E R E N C E If you skipped the chapter on the CustomUI Editor or need a refresher, see Chapter 2.
In order to add a custom image, you must use the image attribute of the control. Suppose you want to add a custom image to a button — the XML code for the button should look something like this:
By now, you’re probably fluent in interpreting such straightforward XML, so we’re just going to point out some helpful factors. In the preceding example, the image name is test_img. Notice that it is unnecessary to specify the file extension; the relationship file takes care of that as well as pointing to the correct location of the image within the XML zipped group. In this example, if you were to inspect the relationship XML file (CustomUI.xml.rels), you’d find the following code snippet:
This relationship file and its XML code are automatically generated by the CustomUI Editor, a very handy tool to use. The next task is to load the images. In order to use the CustomUI Editor to load custom images, follow these steps: 1. Open your workbook/document using the CustomUI Editor. 2. Add your UI XML code. Wherever you wish to add an image, use the image attribute and specify the filename — for example, image=”test_img”. You do not need to worry about file extensions. 3. Click the Insert Icon button on the CustomUI Editor toolbar. 4. Browse to the folder containing the images and open the image files.
91118c08.qxd:WileyRedTight
268
Part I
■
11/28/07
9:16 PM
Page 268
The Building Blocks for a Successful Customization
Figure 8-5 shows the test_img file added through the CustomUI Editor. Notice that the CustomUI Editor now has an extra column to list all images attached to the project.
Figure 8-5: Adding custom pictures using the CustomUI Editor
This is a very easy way to load custom pictures to your UI, but it can also be very time consuming, especially if you have a lot of pictures to load. An alternative is to load the pictures on-the-fly, so we’ll cover that next.
Loading Custom Pictures On-the-Fly You now know how to load custom pictures using the CustomUI Editor, and how to identify your custom pictures in the UI. However, there may be times when you need to swap images on-the-fly, or when the volume of images is simply too large to justify a mega operation to write the XML code; in other words, you would spend more time writing XML code than anything else. In such situations, the images should not be bundled together with the XML files that make up your Excel workbook or Word document. Doing so would cause the images to sit statically on the UI because the UI will be built at designtime, not at run-time. If you need to swap images on-the-fly — for example, making a change at run-time — then you need to load the images using the getImage attribute of the control. However, this brings us to yet another problem. In this case, the method for loading an image is normally through the VBA LoadPicture function. As pointed out previously, this function cannot handle PNG files, which are the best picture format for the Ribbon. We’ll start with something simple before getting to the details of loading custom PNG pictures. Our first example will load common BMP pictures into a toggleButton, as shown in the following code. We’ve chosen BMP files because they are very common.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 269
Chapter 8
■
Custom Pictures and Galleries 269
After we cover how to load PNG images, which support full transparency, you can compare the results. Note that the method used to load bitmap files can also be used for icon files and Windows metafiles such as FileName.ico, FileName.wmf, and so on.
You use the getImage attribute to define a callback that will handle the loading of the picture. In this example, the callback is named as rxtgl_getImage and has the following signature: rxtgl_getImage(control as IRibbonControl, ByRef returnedVal)
Because this example uses a toggleButton, you need to keep track of the toggle state — that is, is the button toggled or not? You can do this in various ways, such as by keeping its value in the registry. However, for our purposes, we will use a global Boolean variable to keep track of it. We also need an onLoad event to invalidate the control and ensure that the image is loaded according to the click of the toggleButton. Hence, we will have the following VBA code in a standard module. Please note that this code is also provided in the download file for this chapter.
N OT E When placing code in modules, you can place them all in the same module. However, you may find it easier to separate the code into different modules for better organization and compartmentalization of the code. For example, you may wish to place callbacks in one module, custom functions in another, and so on. Remember to save the modules and name them in accordance with whatever naming conventions you have adopted. Dim grxIRibbonUI As IRibbonUI Dim gblnPressed As Boolean Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) Set grxIRibbonUI = ribbon End Sub Sub rxtgl_getImage(control As IRibbonControl, ByRef returnedVal) Set returnedVal = LoadImage(ThisWorkbook.Path & “\mex.bmp”) If gblnPressed Then Set returnedVal = _ LoadImage(ThisWorkbook.Path & “\usa.bmp”) End Sub Sub rxtgl_Click(control As IRibbonControl, pressed As Boolean) gblnPressed = pressed grxIRibbonUI.InvalidateControl (“rxtgl”) End Sub
91118c08.qxd:WileyRedTight
270
Part I
■
11/28/07
9:16 PM
Page 270
The Building Blocks for a Successful Customization
Figure 8-6 shows the customized toggleButton. Note that the labels for the images are static.
Figure 8-6: Swapping images on-the-fly
If you wish, you can use the getLabel attribute instead of the Label attribute to dynamically change the labels as you toggle and release the toggleButton. The following lines of code are enough to get that done: Sub rxtgl_getLabel(control As IRibbonControl, ByRef returnedVal) returnedVal = “Mexico Rocks!” If gblnPressed Then returnedVal = “The US Rocks!” End Sub
N OT E Before using the preceding code, ensure that you have replaced the Label attribute with the getLabel attribute, as they cannot coexist in the XML code.
Adding Custom Pictures to Access Projects Access, as you have already seen, is unique when it comes to customization. You will remember that you added your XML code to a table, loaded the UI, chose the UI, and then had to close and reopen the file before you could have a glimpse of the new UI. Just as you do not attach your XML UI file to a bundle of compressed XML files that make up an Excel workbook or Word document, you cannot bundle pictures in Access using this method. Instead, you have to rely on other means. Despite Access’s uniqueness, one of the methods presented for Excel and Word also works for Access: using the getImage attribute to specify a callback that will load the images into Access on-the-fly. Loading on-the-fly is not always going to be the desired approach, though, so another option is to store the images in an attachment field (a great new feature in Access 2007) and then retrieve these files and load them to the UI. This is akin to bundling the image files in the compressed format for Excel and Word. Storing the images within the file avoids concerns about shipping images separately or suddenly missing images.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 271
Chapter 8
■
Custom Pictures and Galleries 271
Use the following steps to store images in an attachment field:
N OT E When adding the fields to the table, ensure that you are in Design View. If you create a field from Datasheet View by typing in the data, you cannot convert the field to an attachment field later. 1. Add a new table and name it USysRibbonImages. (You may use a different name, but this name is used in the following code and examples). 2. Add the following fields to the new table: ■■
ID (an auto numbering field)
■■
ImageName (a text field)
■■
ImageFile (an attachment field)
3. Save and close the table. 4. With the table selected (but still closed), go to Create ➪ Forms ➪ Form and create a new form based on the USysRibbonImages table. Next, you need to fill in the details for each image and attach the image files to their corresponding fields in the table. You are, in fact, simply adding records to the table. After you have added a couple of records, the table (in Datasheet View) should look similar to the table shown in Figure 8-7.
Figure 8-7: Adding image files as records in an attachment field
With the images attached to the fields and the table ready, you can now write the VBA code that will handle the swapping (loading) of the two images. We used the following code placed in a standard module (to add a standard module, click Create ➪ Other ➪ Macro ➪ Module): ‘Declaration of global variables Dim grxIRibbonUI As IRibbonUI Dim gblnPressed As Boolean Dim gFrmImages As Form Dim grstForm As DAO.Recordset2 Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) ‘ Open the form containing the records for the images DoCmd.OpenForm “USysRibbonImages”, , , , acFormReadOnly, acHidden
91118c08.qxd:WileyRedTight
272
Part I
■
11/28/07
9:16 PM
Page 272
The Building Blocks for a Successful Customization
‘
Set the global form as being the USysRibbonImages form Set gFrmImages = Forms(“USysRibbonImages”)
‘
Set the form recordset Set grstForm = gFrmImages.Recordset Set grxIRibbonUI = ribbon
End Sub Sub rxtgl_getImage(control As IRibbonControl, ByRef returnedVal) ‘
If the toggleButton is not pressed, then load the Mexican flag by... If Not (gblnPressed) Then ‘ ... finding the Mexican flag record... grstForm.FindFirst “ImageName=’mex.bmp’“ ‘ ... and setting the toggleButton image ‘ equal to the image attachment Set returnedVal = gFrmImages.imagefile.PictureDisp Else ‘ Otherwise, load the American flag image grstForm.FindFirst “ImageName=’usa.bmp’“ Set returnedVal = gFrmImages.imagefile.PictureDisp End If End Sub Sub rxtgl_Click(control As IRibbonControl, pressed As Boolean) gblnPressed = pressed grxIRibbonUI.InvalidateControl (“rxtgl”) End Sub
This method works fine, but you should also know that all of the images can be stored in a single record. You can do this in conjunction with using the USysRibbons table to save time and keep customization-specific images in a single location. Keep in mind that there may be more than one customization stored in the table, and each one of these customizations may have its own set of images. Now it is time to demonstrate this technique. Using your current file, follow these steps: 1. Add a new field to the USysRibbons table and set its data type to attachment. In our example, the field is called RibbonImages. 2. Create a new form based on the USysRibbons table. (By default it will have the same name as the table; you can keep the name or change it if you like.) 3. Attach to this field all of the images that you want to use. You may want to match the attachments to each UI you have on the table. The attachment field will show the total number of pictures attached to it, as shown in Figure 8-8.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 273
Chapter 8
■
Custom Pictures and Galleries 273
Figure 8-8: Adding image files as attachments to a single record
Next, it is time to write the VBA that will handle loading the image for the toggleButton. We used the following code in a standard module (you can create a
new module or add this to an existing module, whichever fits best with your standard practices and naming conventions): Dim grxIRibbonUI Dim gblnPressed Dim gAttachment
As IRibbonUI As Boolean As Attachment
Sub rxIRibbonUI_onLoad(ribbon As IRibbonUI) ‘ ‘
Open the form and set the attachment as being the attachment control on the form DoCmd.OpenForm “USysRibbons”, , , , acFormReadOnly, acHidden Set gAttachment = Forms(“USysRibbons”).Controls(“RibbonImages”) Set grxIRibbonUI = ribbon
End Sub Sub rxtgl_getImage(control As IRibbonControl, ByRef returnedVal) ‘ Set the image according to the need. Use the file name to ‘ refer to the image you want to load Set returnedVal = gAttachment.PictureDisp(“usa.bmp”) If Not (gblnPressed) Then Set returnedVal = _ gAttachment.PictureDisp(“mex.bmp”) End Sub
Everything looks great except for one significant detail: the image is a BMP that displays with a white background, which makes it look plain ugly. As previously mentioned, you cannot load PNG images to the UI using the common methods applied here; instead, you need to use Windows APIs. That is covered in the next section, so keep working.
91118c08.qxd:WileyRedTight
274
Part I
■
11/28/07
9:16 PM
Page 274
The Building Blocks for a Successful Customization
Using GDI+ to Load PNG Files This section describes how to use the GDI+ (Graphics Device Interface Plus) APIs to load PNG files. However, because many of you may not be familiar with GDI+, we’ll take just a moment to provide a definition before jumping into the examples. GDI+ is one of the core subsystems of Windows. It is used as an interface for representing graphical objects when you need to send these objects to devices such as displays and/or printers. The GDI+ handles the rendering of various drawing objects, and here we use it to render PNG objects so that they can be loaded onto the UI. Unfortunately, you cannot use PictureDisp, as you did in the preceding section, to refer to a PNG file, let alone use the LoadPicture function to load a picture from an external location. You need to use some other technique to load your pictures. The method you will use involves the implementation of some Windows APIs, whose explanation is beyond the scope of this book. Moreover, the code for such a task is quite long, so we will not repeat it here; however, you can find it in the accompanying sample files in the download for this chapter on the book’ website. Because the focus of this example is loading images, you can use the examples from the previous sections and thereby use the same XML. Right now, it is only VBA code that we’re concerned about. The User Defined Function (UDF) based on GDI+ APIs is called LoadImage. Hence, if you wish to load images in Excel or Word, all you need to do is replace the LoadPicture function with the LoadImage function. The VBA code relating to the getImage attribute would now look like the following after making the appropriate changes: Sub rxtgl_getImage(control As IRibbonControl, ByRef returnedVal) Set returnedVal = LoadImage(ThisWorkbook.Path & “\mex.png”) If gblnPressed Then Set returnedVal = _ LoadImage(ThisWorkbook.Path & “\usa.png”) End Sub
You can use the same method in Access, but be aware that you also have other options. For example, suppose you used a table containing an attachment field to store your PNG images (like the one you used in the first Access example). In that case, you need to modify the rxtgl_getImage callback. You also need to declare and instantiate a new variable. To get started, open the VBE and, in the General Declarations area of your standard module, declare the following variable: Dim gstrTempFolderFile
As String
For the onLoad event, you only need to set the IRibbonUI object. The form and attachment objects used previously will not be necessary this time. The rxtgl_Click event will remain the same. The big change occurs in the rxtgl_getImage callback. For that, we use the following code: Sub rxtgl_getImage(control Dim rstData As Dim rstImages As Dim db As
As IRibbonControl, ByRef returnedVal) DAO.Recordset DAO.Recordset DAO.Database
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 275
Chapter 8
■
Custom Pictures and Galleries 275
Dim objWSHShell As Object Dim strSQL As String Set db = CurrentDb If gstrTempFolderFile = “” Then Set objWSHShell = CreateObject(“WScript.Shell”) gstrTempFolderFile = _ objWSHShell.SpecialFolders(“Desktop”) & “\temp.png” End If strSQL = “SELECT * FROM RibbonImages WHERE ImageName=’usa.png’“ If gblnPressed Then strSQL = _ “SELECT * FROM RibbonImages WHERE ImageName=’mex.png’“ On Error Resume Next Set rstData = db.OpenRecordset(strSQL, dbOpenDynaset) Set rstImages = rstData.Fields(“ImageFile”).Value rstImages.Fields(“FileData”).SaveToFile gstrTempFolderFile Set returnedVal = LoadImage(gstrTempFolderFile) Kill gstrTempFolderFile db.Close rstData.Close rstImages.Close Set Set Set Set
db = Nothing rstData = Nothing rstImages = Nothing objWSHShell = Nothing
End Sub
Now that you have the code in front of you, take a moment to review what it is doing: 1. You create a Shell object so that you can temporarily save the image to a location in your hard drive. Our example uses the Desktop as the temporary location. 2. You define a SQL instruction to retrieve the image and save it to the defined location (directly on the Desktop) using the SaveToFile method. 3. The image is loaded using the LoadImage function. 4. The temporary image is deleted from the Desktop. 5. You close with some good housekeeping techniques.
N OT E If you are using Windows Vista with User Account Control switched on, you must choose an area of the hard drive for which you have permission to write. To avoid undue complications, we chose the Desktop as the destination of the temporary PNG file.
91118c08.qxd:WileyRedTight
276
Part I
■
11/28/07
9:16 PM
Page 276
The Building Blocks for a Successful Customization
Using the Gallery Control The gallery control is new to Office 2007 and was conceived in conjunction with the Ribbon. The gallery was designed as a way to graphically display user options; an excellent example is the Styles gallery, which enables users to select a style by looking at a graphical representation of the style, rather than a name. You can use galleries for such tasks as organizing photos, accessing styles, or storing color palettes. A gallery is the perfect control for a customization that requires and provides visual impact. Figure 8-9 shows a photo gallery.
Figure 8-9: A two-column photo gallery
Note that you can also add buttons at the bottom of the gallery. With such versatility, the uses for a gallery are limited only by your imagination. Before we get into the code for a gallery, let’s briefly review how this example works. It is advisable that you download all files for this chapter before continuing. We have created various examples of image galleries for your reference. As you can see from the image gallery examples in Access, each image represents an item in the gallery — these items are loaded from images contained in a folder. When you click on the icon to expand the gallery, it will list the images contained in the folder; clicking on a specific image will open the folder that contains the actual image files that are represented in the gallery. Note that our example only displays the PNG version of the image in the gallery, but the folders actually contain both a PNG and a BMP version of the selected photos. When users click on an image, they will get a standard message from Access advising them that they are about to open the folder with the gallery images. Clicking Yes will then open the selected folder using Windows Explorer, so it will show all the files, not just the image files. At that point, the behavior of the files and images is controlled by the default setting on the computer. If you click on an image, it will open with the
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 277
Chapter 8
■
Custom Pictures and Galleries 277
default program, such as Windows Picture and Fax Viewer (you can open the image using its associated program directly from your project through a Shell function, for example). The markup for a gallery is as follows:
A gallery is made up of static, dynamic, and optional child attributes. It requires each of the static attributes shown in Table 8-2. Table 8-2: Gallery Static Attributes STATIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
columns
1 to 1024
N/A
itemHeight
1 to 4096
Sub getItemHeight (control As IRibbonControl, ByRef height)
itemWidth
1 to 4096
Sub getItemWidth (control As IRibbonControl, ByRef width)
rows
1 to 1024
N/A
sizeString
1 to 1024 characters
N/A
showItemImage
true, false, 1, 0
N/A
showItemLabel
true, false, 1, 0
N/A
A gallery may also have all of the dynamic attributes shown in Table 8-3. Table 8-3: Gallery Dynamic Attributes DYNAMIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
getItemCount
0 to 1000
Sub GetItemCount (control As IRibbonControl, ByRef count)
getItemID
Sub GetItemID (control As IRibbonControl, index As Integer, ByRef id)
getItemImage
Sub GetItemImage (control As IRibbonControl, index As Integer, ByRef image) Continued
91118c08.qxd:WileyRedTight
278
Part I
■
11/28/07
9:16 PM
Page 278
The Building Blocks for a Successful Customization
Table 8-3 (continued) DYNAMIC ATTRIBUTE
ALLOWED VALUES
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
getItemLabel
1024
Sub GetItemLabel (control As IRibbonControl, index As Integer, ByRef label)
getItemScreentip
1024
Sub GetItemScreenTip (control As IRibbonControl, index As Integer, ByRef screentip)
getItemSupertip
1024
Sub GetItemSuperTip (control As IRibbonControl, index As Integer, ByRef supertip)
getSelectedItemID
1 to 1024 characters
Sub GetSelectedItemID (control As IRibbonControl, ByRef index)
getSelectedItemIndex
1 to 1024
Sub GetSelectedItemIndex (control As IRibbonControl, ByRef index)
onAction
Sub OnAction (control As IRibbonControl, selectedId As String, selectedIndex As Integer)
Finally, a gallery can take either or both of the objects shown in Table 8-4 as its child elements; it can also have multiple instances of each. Table 8-4: Child Elements of a Gallery OBJECT
WHAT IT IS FOR
button
Add a clickable button to the gallery in the same way you can add a clickable button to a group, for example.
item
Adds an item to the gallery. The item can be a graphical representation of some action that you want to perform, such as a graphical representation of a chart layout.
Now that you’ve seen the list of gallery attributes, let’s take a look at the benefits and uses of each category.
Example of Static Attributes Using static values for attributes means that the customization cannot be modified after the UI has been loaded. Remember that the Ribbon is built at design-time, not at run-time. After the UI is loaded, limited changes may be possible if the control is dynamic or has dynamic attributes.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 279
Chapter 8
■
Custom Pictures and Galleries 279
Our demonstration walks through creating the gallery shown in Figure 8-9. In order to create a gallery, follow these steps: 1. Create your Excel or Word file and ensure that it is macro-enabled. 2. Save and close the file. Open it using the CustomUI Editor. 3. Paste the following XML code into it:
4. Load the images named img0 through img5 using the CustomUI Editor.
91118c08.qxd:WileyRedTight
280
Part I
■
11/28/07
9:16 PM
Page 280
The Building Blocks for a Successful Customization
C R O S S-R E F E R E N C E For more detailed instructions on this method of loading custom images, refer to the section “Using the Custom UI Editor” earlier in this chapter.
Example of Built-in Controls A good use for built-in galleries is to consolidate into one location those tools you use most often. For example, you may have galleries under the Home, Insert, and Page Layout tabs that you want to bring together under a custom tab so that you have them all in one place. That way, you don’t need to navigate from one tab to another, which can be distracting and time consuming. As you know, you refer to built-in controls by invoking the idMso attribute for the control that you want. The following example brings together three gallery controls onto a custom tab and group in Excel:
Reviewing the code, you may have noticed two controls that we haven’t yet covered: labelControl and box. Chapter 10 explains the features and attributes of these controls, as well as how to work with them. For now, we want to stay focused on working with images. Figure 8-10 shows our new consolidation and custom group.
Figure 8-10: Consolidated built-in galleries
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 281
Chapter 8
■
Custom Pictures and Galleries 281
Creating an Image Gallery On-the-Fly The previous example is fine as long as you do not have to load a lot of images. As the number of images in your gallery increases, you need to type more into the XML code. Obviously, this is not only time consuming but also complicated to maintain. The following example helps you avoid that extra typing. Although it is for Access, it can also be used in Excel or Word. In fact, this example is drawn from the previous one, with the difference that this dynamically loads the images. You will also notice two new attributes, as well as one that you’ve already seen. ■■
getImage: You have already seen this attribute. You will use it to load the front
picture of the gallery (not the actual gallery pictures). ■■
getItemCount: This attribute is used to return the total number of items in the gallery. You will use a constant in the VBA code to determine this value so that you can easily change the item count if you need.
■■
getItemImage: This attribute is used to load the item image — that is, each image that appears in the gallery.
Note that you do not run a loop through each item; instead, the Ribbon will call back on the getItemImage attribute (hence the “callback” terminology) until it runs through all the items determined for the getItemCount attribute. As it does so, it returns an index number for each item’s image. You then use this index to grab and load each corresponding picture. The XML code for this customization is as follows:
91118c08.qxd:WileyRedTight
282
Part I
■
11/28/07
9:16 PM
Page 282
The Building Blocks for a Successful Customization
Save the XML code to your USysRibbon table in Access and add a standard module to which you add the following callbacks: Public Const gcItemCount = 6 Sub rxgal_getItemCount(control As IRibbonControl, ByRef returnedVal) returnedVal = gcItemCount End Sub Sub rxgal_getItemImage(control As IRibbonControl, index As Integer, _ ByRef returnedVal) Set returnedVal = _ LoadPicture(CurrentProject.Path & “\img” & index & “.bmp”) End Sub Sub rxgal_getImage(control As IRibbonControl, ByRef returnedVal) Set returnedVal = LoadPicture(CurrentProject.Path & “\img4.tif”) End Sub Sub rxgal_Click(control As IRibbonControl, id As String, _ index As Integer) MsgBox “You have click on image index number “ & index End Sub
You are now probably questioning the BMP format. No worries — if you need to load the suggested format (PNG) you can use the LoadImage function to do so. The Access project that accompanies this example uses the LoadImage function to load PNG files.
Conclusion In this chapter, you learned the intricacies of dealing with custom images in a custom UI. You learned what file formats are appropriate and which sizes are ideal, as well as scaling and resolution of such files.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 283
Chapter 8
■
Custom Pictures and Galleries 283
You went through the process of manually loading images, and used the LoadPicture function and a more specialized UDF wrapper function based on a Windows API. You also learned some unique ways of storing and retrieving images from an attachment field of an Access table to be used in your UI and how to build custom galleries and consolidate built-in galleries. Now you are ready to add a very personal touch to an already personalized working environment, so go have fun with your photos and other images. The next chapter describes how to create menus and how to use the splitButton and dynamicMenu controls to organize your UI.
91118c08.qxd:WileyRedTight
11/28/07
9:16 PM
Page 284
91118c09.qxd:WileyRedTight
12/2/07
3:32 PM
Page 285
CHAPTER
9 Creating Menus
In implementing the Ribbon, one of Microsoft’s primary goals was to provide a more intuitive user interface and to move away from the old menu-driven paradigm that had historically housed Office’s commands. Therefore, it may come as a bit of a surprise to find out that menus are still very much alive and well. In fact, in selected scenarios they actually form the most dynamic of all the controls that the Ribbon has to offer. The comboBox and dropDown controls you learned about in Chapter 7 can be viewed as menu-type controls, in that they both provide a label next to an empty box; and by clicking that box, the user is provided a list of items to pick from. This chapter deals with three other menu-type controls: the menu, the splitButton and the dynamicMenu. Unlike the comboBox and dropDown controls, the menu and splitButton controls provide their menus behind a button-style interface from which the options become available. The dynamicMenu is similar in purpose to the menu, but actually creates a full menu on-the-fly. This chapter provides a detailed discussion of these three controls and includes examples showing how to create and use each one. As you are preparing to work through the examples in this chapter, we encourage you to download the companion files. The source code and files can be found on the book’s website at www.wiley.com/ go/ribbonx.
285
91118c09.qxd:WileyRedTight
286
Part I
■
12/2/07
3:32 PM
Page 286
The Building Blocks for a Successful Customization
The menu Element On the surface, the menu is very similar in purpose to the dropDown controls we created in Chapter 7. A menu provides the user with a pre-defined list of options to pick from, and like the dropDown it can incorporate both images and text. Why learn about the menu control if the dropDown does the same thing? One major limitation of a dropDown control is that it can only hold “items,” whereas a menu control can hold a wide variety of other controls, including buttons, checkboxes, galleries, and even another menu. Although we haven’t yet demonstrated these other controls in the examples, you can appreciate that the capability to nest all of these elements within a menu gives it very rich formatting possibilities, and thereby makes the dropDown seem quite plain in comparison. In addition, the default display of the dropDown is an empty box, but the menu can be configured with a “face” that is independent of the actual items that appear in its list. Given that appearance and first impressions matter, being able to give the menu a face is a rather attractive feature. The menu control also has an attribute that can be used to draw lines between controls in the menu. While the menuSeparator attribute is not explored until Chapter 10, it is nonetheless one of the reasons why the menu control is used; used together, these elements enable you to bring controls together in groups and then organize them into subgroups.
Required Attributes of the menu Element To create a menu element, you need to define one, and only one, of the id attributes shown in Table 9-1. Table 9-1: Required Attributes of the menu Element ATTRIBUTE
WHEN TO USE
id
When creating your own menu
idMso
When using an existing Microsoft menu
idQ
When creating a menu shared between namespaces
Optional Static and Dynamic Attributes with Callback Signatures The menu element will optionally accept any one insert attribute shown in Table 9-2.
91118c09.qxd:WileyRedTight
12/2/07
3:32 PM
Page 287
Chapter 9
■
Creating Menus
Table 9-2: Optional insert Attributes of the menu Element INSERT ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
WHEN TO USE
insertAfterMso
Valid Mso Group
Insert at end of group
Insert after Microsoft control
insertBeforeMso
Valid Mso Group
Insert at end of group
Insert before Microsoft control
insertAfterQ
Valid Group idQ
Insert at end of group
Insert after shared namespace control
insertBeforeQ
Valid Group idQ
Insert at end of group
Insert before shared namespace control
In addition to the insert attribute, you may also include any of the optional static attributes, or their dynamic equivalents, listed in Table 9-3. Table 9-3: Optional Attributes and Callbacks of the menu Element STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
description
getDescription 1 to 1024 characters
enabled
getEnabled
image
DEFAULT VALUE
VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
(none)
Sub GetDescription (control As IRibbonControl, ByRef returnedVal)
true, false, 1, 0
true
Sub GetEnabled (control As IRibbonControl, ByRef returnedVal)
getImage
1 to 1024 characters
(none)
Sub GetImage (control As IRibbonControl, ByRef returnedVal)
imageMso
getImage
1 to 1024 characters
(none)
Same as above
itemSize
(none)
normal, large
normal
(none)
keytip
getKeytip
1 to 3 characters
(none)
Sub GetKeytip (control As IRibbonControl, ByRef returnedVal) Continued
287
91118c09.qxd:WileyRedTight
288
Part I
■
12/2/07
3:32 PM
Page 288
The Building Blocks for a Successful Customization
Table 9-3 (continued) VBA CALLBACK SIGNATURE FOR DYNAMIC ATTRIBUTE
STATIC ATTRIBUTE
DYNAMIC ATTRIBUTE
ALLOWED VALUES
DEFAULT VALUE
label
getLabel
1 to 1024 characters
(none)
Sub GetLabel (control As IRibbonControl, ByRef returnedVal)
screentip
getScreentip
1 to 1024 characters
(none)
Sub GetScreentip (control As IRibbonControl, ByRef returnedVal)
showImage
getShowImage true, false, 1, 0
true
Sub GetShowImage (control As IRibbonControl, ByRef returnedVal)
showLabel
getShowLabel true, false, 1, 0
true
Sub GetShowLabel (control As IRibbonControl, ByRef returnedVal)
size
getSize
normal, large
normal
Sub GetSize (control As IRibbonControl, ByRef returnedVal)
supertip
getSupertip
1 to 1024 characters
(none)
Sub GetSupertip (control As IRibbonControl, ByRef returnedVal)
tag
(none)
1 to 1024 characters
(none)
(none)
visible
getVisible
true, false, 1, 0
true
Sub GetVisible (control As IRibbonControl, ByRef returnedVal)
Allowed Children Objects of the menu Element The menu element will accept any combination of the following child objects: ■■
button
■■
checkbox
■■
control
■■
dynamicMenu
■■
gallery
91118c09.qxd:WileyRedTight
12/2/07
3:32 PM
Page 289
Chapter 9 ■■
menu
■■
menuSeparator
■■
splitButton
■■
toggleButton
■
Creating Menus
Parent Controls of the menu Element The menu element may be used within the following controls: ■■
box
■■
buttonGroup
■■
dynamicMenu
■■
group
■■
menu
■■
officeMenu
■■
splitButton
Graphical View of menu Attributes Figure 9-1 displays a custom Ribbon group in Word with a menu control. Because the menu control is shown in its expanded form, you cannot see the screentip, supertip, or keytip associated with the custom Save As menu, but they are available. The figure also illustrates the effect of the itemSize attribute, showing the menu items with a setting of “normal,” rather than “large.”
Figure 9-1: Visible attributes of the menu element
Despite the fact that this is a completely customized Save As menu, it will probably look quite intuitive. The “97-2003 Files” and “2007 Files” headers are both menuSeparator elements, which instantly add clarity by grouping the controls. The menuSeparator can
289
91118c09.qxd:WileyRedTight
290
Part I
■
12/2/07
3:32 PM
Page 290
The Building Blocks for a Successful Customization
also provide the dividing line, as shown in the heading lines “97-2003 Files” and “2007 Files.” Chapter 10 explains how to add the various menuSeparator elements to a customization.
Using Built-in Controls Microsoft exposes several menu controls for our use, so let’s take a look at how you would go about using one of them. The Prepare menu is available in both Excel and Word. It is usually located on the Office Menu (see Figure 9-2).
Figure 9-2: The Prepare menu in Excel
However, let’s assume that due to frequent usage, it would save the users time if this were conveniently located on a custom Ribbon tab instead of being buried on the Office button. The first thing that you would want to do is create a new file to hold the code. Open either Excel or Word, create a new document, and save it in the macro-free file format (xlsx or docx, respectively). That’s a benefit of working with a built-in control. No VBA code is required to make this work, so the file can be saved in a macrofree format, and the customizations will be fully functional. Once the file is saved, close the application.
C R O S S-R E F E R E N C E Chapter 17 provides a more complete discussion of security and enabling macros.
Launch the CustomUI Editor and open the file within it. Apply the RibbonBase template that you created in Chapter 2, and insert the following code between the and tags:
91118c09.qxd:WileyRedTight
12/2/07
3:32 PM
Page 291
Chapter 9
■
Creating Menus
Once you have validated your code, save it and close the file in the CustomUI Editor. Reopen the file in its native application and check the Demo tab. As shown in Figure 9-3, the menu has smoothly moved into a Ribbon group.
Figure 9-3: The “FilePrepareMenu” on a custom tab
Of course, this is actually just a copy of the Prepare menu, so it will still be in the default location under the Office button. That is only logical, as users are accustomed to finding commands where Microsoft places them. In other words, unless you use the startFromScratch setting, discussed in Chapter 13, customizations will merely create additional links to built-in commands.
Creating Custom Controls With so many controls on the Ribbon, it is nice to know that you can use Microsoft’s builtin controls pretty much wherever you’d like. It is relatively easy to provide convenient, custom groups of the controls that an individual (or department) uses the most. However, as you begin to design your own user interfaces, it will quickly become apparent that the
291
91118c09.qxd:WileyRedTight
292
Part I
■
12/2/07
3:32 PM
Page 292
The Building Blocks for a Successful Customization
built-in controls are just not enough. This is why we now turn our focus to some practical examples that demonstrate why you might want to create custom menu controls, and how you can do that for each of the three applications. The menu control is used in a variety of examples throughout the book, so you’ll have more opportunities to use it in conjunction with other controls. In fact, you’ll use it later in this chapter with the splitButton control.
An Excel Example For this example, we create a menu that holds a few useful Internet links. It will be contained on a custom group called “Ribbon Help” and placed at the end of the Developer tab. Naturally, Microsoft cannot provide us with this exact menu, because we want it to contain our preferred links. That means that we need to create the entire menu from scratch. That requires VBA code to react to the selected items, so open Excel, create a new workbook, and save it in the macro-enabled (xlsm) format. After saving the file, close Excel and open the file in the CustomUI Editor. Apply the RibbonBase template (created in Chapter 2) to the file, and enter the following code between the and tags: