jimmyid's picture
From jimmyid rss RSS  subscribe Subscribe

Professional Outlook 2007 Programming Programmer To Programmer.9780470049945.29619 



Professional Outlook 2007 Programming Programmer To Programmer.9780470049945.29619

 

 
 
Tags:  dedicated  hosting  server 
Views:  569
Downloads:  8
Published:  January 12, 2010
 
1
download

Share plick with friends Share
save to favorite
Report Abuse Report Abuse
 
Related Plicks
Afrihost - South African Dedicated Web Servers

Afrihost - South African Dedicated Web Servers

From: amgreene
Views: 220 Comments: 0
Afrihost - South African Dedicated Web Servers
 
Afrihost - South African Dedicated Web Servers

Afrihost - South African Dedicated Web Servers

From: bugzyu
Views: 182 Comments: 0

 
Quick Reference Guide: Server Hosting

Quick Reference Guide: Server Hosting

From: anon-523608
Views: 99 Comments: 0

 
Hostgator dedicated server

Hostgator dedicated server

From: astark
Views: 277 Comments: 0
Hostgator dedicated server
 
See all 
 
More from this user
Documents About [Car Insurance Online]

Documents About [Car Insurance Online]

From: jimmyid
Views: 419
Comments: 0

Thanksgiving In Usa

Thanksgiving In Usa

From: jimmyid
Views: 373
Comments: 0

Aedan Ryan of PuddleDucks.ie talks about his Business Blog

Aedan Ryan of PuddleDucks.ie talks about his Business Blog

From: jimmyid
Views: 314
Comments: 0

Samsung LN40A450 40-Inch 720p LCD HDTV

Samsung LN40A450 40-Inch 720p LCD HDTV

From: jimmyid
Views: 447
Comments: 0

Alienware M17x-2857DSB 17-Inch Laptop (Black)

Alienware M17x-2857DSB 17-Inch Laptop (Black)

From: jimmyid
Views: 76
Comments: 0

Questar 3Q-2008 EARNINGS

Questar 3Q-2008 EARNINGS

From: jimmyid
Views: 215
Comments: 0

See all 
 
 
 URL:          AddThis Social Bookmark Button
Embed Thin Player: (fits in most blogs)
Embed Full Player :
 
 

Name

Email (will NOT be shown to other users)

 

 
 
Comments: (watch)
 
 
Notes:
 
Slide 2: Professional Outlook® 2007 Programming Ken Slovak Wiley Publishing, Inc.
Slide 4: Professional Outlook® 2007 Programming
Slide 6: Professional Outlook® 2007 Programming Ken Slovak Wiley Publishing, Inc.
Slide 7: Professional Outlook® 2007 Programming Published by Wiley Publishing, Inc. 10475 Crosspoint Boulevard Indianapolis, IN 46256 www.wiley.com Copyright © 2007 by Wiley Publishing, Inc., Indianapolis, Indiana Published simultaneously in Canada ISBN-13: 978-0-470-04994-5 Manufactured in the United States of America 10 9 8 7 6 5 4 3 2 1 Library of Congress Cataloging-in-Publication Data: Slovak, Ken. Professional Outlook 2007 programming / Ken Slovak. p. cm. Published simultaneously in Canada. Includes index. ISBN 978-0-470-04994-5 (paper/website) 1. Microsoft Outlook. 2. Personal information management. 3. Business--Computer programs. I. Title. HF5548.4.M5255S583 2007 005.5'7--dc22 2007031694 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 please contact our Customer Care Department within the United States at (800) 762-2974, outside the United States at (317) 572-3993 or fax (317) 572-4002. Trademarks: Wiley, the Wiley logo, Wrox, the Wrox logo, Wrox Programmer to Programmer, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in the United States and other countries, and may not be used without written permission. Outlook and Microsoft are registered trademarks of Microsoft Corporation in the United States and/or other countries. All other trademarks are the property of their respective owners. Wiley Publishing, Inc., is not associated with any product or vendor mentioned in this book. Wiley also publishes its books in a variety of electronic formats. Some content that appears in print may not be available in electronic books.
Slide 8: This book is dedicated, as always, to my beloved wife, Susie, for her patience in putting up with the long hours of writing and for just being there, and to my dog Casey for keeping me company while I was writing this book and for being my sales manager and the administrator of my Exchange server.
Slide 9: About the Author Ken Slovak is president of Slovak Technical Services, a company specializing in Outlook, Exchange, and Office custom development and consulting. He has been an Outlook MVP since 1998. He has coauthored Programming Microsoft Outlook 2000, Professional Programming Outlook 2000, Beginning Visual Basic 6 Application Development; contributed material to other Outlook books; and written numerous magazine articles about Outlook. He makes his home in central Florida with his wife and dog and enjoys swimming, fishing, cooking, and chasing squirrels for the dog’s amusement.
Slide 10: Credits Acquisitions Director Jim Minatel Production Manager Tim Tate Development Editor Maureen Spears Project Coordinator Lynsey Osborne Technical Editor Diane Poremsky Compositor Craig Johnson, Happenstance Type-O-Rama Production Editor Christine O’Connor Proofreader Christopher Jones Copy Editor Foxxe Editorial Services Indexer Jack Lewis Editorial Manager Mary Beth Wakefield Anniversary Logo Design Richard Pacifico
Slide 11: Acknowledgments A book like this is a team effort, and even though only the author’s name is on the cover, everyone on the team makes valuable contributions. I’d like to thank the editors at Wrox for all their work on the book, which I appreciate more than I can ever express, and for their patience during the long waits for new chapters. Maureen Spears has been a patient and painstaking development editor, guiding the book from beginning to end. Jim Minatel, the acquisitions editor is an old friend and also has been a patient and critical part of this book’s publication. Everyone else at Wrox has also been great to work with. The technical editor, Diane Poremsky, an Outlook MVP, is an old friend and writing partner, and I was lucky to have her on this book. The editors are responsible for everything that’s correct in this book and nothing that's incorrect. I constantly learn more about Outlook and Outlook development from my fellow Outlook MVPs, especially the ones who also are involved with Outlook development: Sue Mosher, Dmitry Streblechenko, Dave Kane, Jay Harlow, Eric Legault, Michael Bauer, Roberto Restelli, and Ricardo Silva. I’d also like to thank the other Outlook and Exchange MVPs for everything I’ve learned from them. There is no better group of MVPs and people, and I appreciate being included in their company. I’d also like to thank Patrick Schmid, who although not an Outlook MVP hangs out with us and who has done a lot of pioneering work with Ribbon development for Office 2007. I’d also like to give special thanks to the people on the Outlook product team responsible for Outlook programmability, Randy Byrne and Ryan Gregg. Randy is an old friend and former Outlook MVP who has helped bring Outlook from the programming Dark Ages by enhancing the Outlook object model to turn it into a complete programming API. Ryan has always been completely helpful and responsive also, and has become a new friend. Finally, Bill Jacob, an Outlook premier support programming specialist in Microsoft PSS, has always been another helpful friend.
Slide 12: Contents Acknowledgments Introduction viii xv Chapter 1: An Introduction to Outlook 2007 Programming Setting Up Outlook VBA Setting Up Macro Security Creating a Code-Signing Certificate Reviewing the VBA Editor Interface and Options 1 1 2 3 4 Understanding Outlook Outlook Outlook Outlook Outlook Profiles and Data Store Sessions and Data Stores Folders Items 5 6 6 6 8 Outlook Data Storage Accessing Data Stores Using NameSpace Accessing Data with Tables 8 9 9 Outlook Data Display Summary 10 11 Chapter 2: What’s New in Outlook 2007 The Unified Object Model The PropertyAccessor Stores and Storage Items User Interface Objects Accounts 13 14 14 24 26 28 Performance Improvements Working with Tables Instead of Items Filtering Tables Table Default Columns Table Limitations 30 30 31 33 33 Security Trusted Code Untrusted Code 34 35 35 The Importance of Using DASL DASL Namespaces Using DASL and JET Syntax 35 36 36 Summary 38
Slide 13: Contents Chapter 3: Outlook Development The Application Object New Methods, Properties, and Events Other Important Collections, Methods, Properties, and Events 39 39 44 48 The NameSpace Object Exchange Categories Picking Folders Picking Names 53 53 54 54 55 Summary 57 Chapter 4: Outlook VBA The Outlook VBA Project The Project File ThisOutlookSession Macros and VBA Procedures 59 59 59 60 60 Macro Security Security Levels Signed Macro Code 60 61 61 Class Modules ThisOutlookSession User Classes 61 62 62 Code Modules Office UserForms Creating the Macro User Interface 63 64 64 Working with Outlook Events Application Events Folder Events User Events Wrapper Classes and Collections 68 69 77 80 81 Macro Projects Custom Rules Additional Macros 88 89 100 Running and Distributing Macros Running Macros Distributing the VBA Project Distributing Individual Macros 108 108 109 110 Summary 110 x
Slide 14: Contents Chapter 5: Outlook Forms Working with Standard Forms Forms Libraries Published Forms Form Customization Advantages and Disadvantages of Custom Forms 111 111 112 113 113 114 Back to the Past: the Forms Development Environment Using the Field Chooser Using the Control Toolbox Using the Code Window 116 118 119 120 Prototyping Forms Code in VBA Simulating the Form Environment in VBA Developing and Debugging Using VBA 122 123 124 Custom Form Walkthrough Creating, Publishing, and Running a Form Where to Publish Forms Publishing Forms Using Code Testing Code for the Form 124 125 129 129 132 Forms Management Form Behavior Trusted Forms Active X Controls Script in Forms 136 138 138 138 139 Form Regions Form Region Locations, Properties, and Registry Settings Form Region Walkthrough 139 140 141 Summary 145 Chapter 6: COM Addins Managed or Unmanaged Code? IDTExtensibility2 and Trusted COM Addins Version Differences VB.NET Addin Implementations C# Addin Implementations Addin Templates 147 147 148 149 149 150 151 Explorer and Inspector Event Handlers Setting Up Explorer and Inspector Event Handlers 152 152 The OnDisconnection Catch-22 Bug Explorer and Inspector Wrappers 155 164 xi
Slide 15: Contents Template Utility Code VB.NET Utility Code C# Utility Code 179 180 182 Displaying Outlook Property Pages Displaying Property Pages with VB.NET Displaying Property Pages with C# 185 185 186 Communicating with a COM Addin VB.NET Testing Addin Communications C# VSTO with VB.NET VSTO with C# 188 188 189 190 191 194 Summary 196 Chapter 7: COM Addins and the Outlook User Interface Working with Menus and Toolbars Menus and Toolbars in Explorers VB.NET C# 197 197 198 199 206 VSTO Interface Handlers VB.NET C# 213 213 214 AxHost VB.NET C# 216 216 216 Working with the Ribbon Ribbon XML Ribbon Callbacks 217 218 224 Custom Task Panes Creating Custom Task Panes Working with Custom Task Panes 234 235 239 Form Regions Creating Custom Form Regions Working with Custom Form Regions 240 242 244 Custom Views Types of Custom Views Creating Custom Views Filtering and Formatting Custom Views View XML 248 248 248 249 250 Summary 251 xii
Slide 16: Contents Chapter 8: Interfacing Outlook with Other Applications Microsoft Word Microsoft Excel Microsoft Access ADO Access DAO DAO DBEngine 253 254 257 264 265 267 268 Web Browser Browser Control Form 269 269 Summary 272 Chapter 9: Real-World Outlook Programming Supporting Multiple Versions of Outlook Coding for Compatibility Ribbon Considerations and Workarounds 275 275 276 277 Addin Deployment Unmanaged Code Addins Managed Code Addins 279 279 280 Outlook Programming Limitations Outlook 2007 Outlook 2003 and Earlier 282 282 283 Things We Still Don’t Have in Outlook 2007 Using Alternate APIs CDO Redemption 287 288 288 289 Summary 303 Chapter 10: Task Management System Task Management System Features The Connect Class The VB Connect Class The C# Connect Class C# MyAxHost VB Globals VB OutExpl C# OutExpl VB ExplWrap C# ExplWrap VB OutInsp 305 305 307 307 319 335 337 337 338 340 341 344 xiii
Slide 17: Contents C# OutInsp VB InspWrap C# InspWrap VB TaskPane C# TaskPane 345 347 353 362 366 VB Ribbon XML C# Ribbon XML Summary 372 373 373 Appendix A: Outlook 2007 Object Model Summary Appendix B: Troubleshooting Problems and Support Index 375 425 439 xiv
Slide 18: Introduction Welcome to Professional Programming Outlook 2007. This book covers Outlook programming, with the primary emphasis on the many new Outlook 2007 programming features. Existing knowledge of Outlook programming isn’t necessary because this book teaches you all you need to know to develop everything from Outlook custom forms and personal productivity macros to advanced COM addins that utilize a range of Microsoft technologies. When I first was briefed on the programming features of Outlook 2007 early in 2005, I felt like a kid at Christmas. Finally, the Outlook object model had almost all the features Outlook developers had been requesting for many years. Outlook’s object model always has been incomplete. Many properties that are important to Outlook developers were always unavailable in previous versions of the Outlook object model. Some examples of properties that were unavailable are Internet headers in emails, the name of the last person to modify an item, the global object ID for calendar items that was introduced in Outlook 2003 SP2 and calendar labels. Advanced Outlook programming always had to use additional programming APIs such as CDO 1.21 (Collaboration Data Objects), Extended MAPI, and third-party libraries that exposed Extended MAPI properties, such as Redemption, that weren’t in the Outlook object model. Now, with Outlook 2007 you rarely if ever have to leave the Outlook object model to do what you want with your code. Who Is This Book For This book is for professional or advanced developers who want to take full advantage of the power of the unified Outlook object model in Outlook 2007, and who want to learn the important new features in the unified object model. Most of the code samples in this book only require Outlook 2007, and use Outlook VBA code. Code that utilizes COM addins or that uses VSTO requires you to install Visual Studio 2005 plus VSTO 2005 SE. What Does This Book Cover? This book covers: ❑ ❑ ❑ The Outlook 2007 object model (OOM). Programming Outlook using VBA, VBScript, VSTO, VB.NET, C#, and VB 6. Outlook macros, custom forms, and COM addins.
Slide 19: Introduction ❑ ❑ ❑ ❑ Importing data into Outlook from other programs such as Word, Excel, and Access. Exporting Outlook data into other programs such as Word, Excel, and Access. Custom user interface elements for Outlook such as menu items, toolbars, and custom Ribbon elements, and the use of custom data input and output forms. Tips and tricks to make your Outlook code run faster and better. Most short code snippets are presented in VBA (Visual Basic for Applications) code. VBA is useful as a prototyping tool, and code in VBA is easily translated into VB.NET, VBScript, VB 6, and usually even into C#. Longer code segments are presented in either VBA or in both VB.NET and C#. The case study is presented using VB.NET and C#. Each chapter introduces important Outlook programming concepts, with plenty of sample code to help you make use of the new and old features of the Outlook object model. In more detail, here’s what you’ll find in each chapter: ❑ ❑ Chapter 1 introduces you to Outlook 2007 programming and includes how to set up Outlook VBA, how to delve into Outlook and the basics on data storage and data display. Chapter 2 covers what’s new in the Outlook object model for Outlook 2007, providing an overview of the important new properties, methods and events in the new unified object model. The chapter also covers new features that can dramatically improve the speed of Outlook code, the essentials of Outlook code security, Outlook syntax, and new features for Outlook forms. Chapter 3 gives you the basics of Outlook development, starting with the decisions about whether to develop using custom Outlook forms, macros or COM addins. It also covers the essential elements of Outlook programming, including the Application, Namespace, Explorer and Inspector objects and collections, Outlook collections and items, and using Outlook’s built-in dialogs. Chapter 4 takes a look at Outlook VBA and macros. Outlook VBA isn’t only for creating simple macros; it’s a great prototyping tool for almost any Outlook project. The VBA Project, macro security, class modules, UserForms, event handling, macro projects and macro distribution are among the topics covered here. Chapter 5 covers Outlook forms, with custom forms both with and without code. The newly exposed form controls such as the calendar controls are discussed, as is prototyping Outlook forms code using VBA, Outlook form publishing and custom form distribution. Chapter 6 shows you Outlook COM addins, both managed and unmanaged, as well as how to use VSTO 2005 SE (Visual Studio Tools for Office) to simplify the development of managed Outlook addins. Creating Outlook property pages, debugging Outlook COM addins and distribution of COM addins are also covered. Chapter 7 covers customizing the Outlook user interface. Working with menus and toolbars as well as the new Office Ribbon interface are covered, as are custom views, custom task panes and form regions. Chapter 8 shows how to interface Outlook with other applications. Interfacing with Word, Excel, Access are all shown. ❑ ❑ ❑ ❑ ❑ ❑ xvi
Slide 20: Introduction ❑ Chapter 9 discusses real world Outlook programming tips and tricks. Code optimization for speed, support for multiple versions of Outlook, working around Outlook’s remaining programming limitations, coping with Outlook security and working with alternate APIs are all covered from the perspective of an Outlook developer. The rest of the material in the book gives you important learning and reference resources in the form of a chapter devoted to a COM addin case study as well as two appendixes. The study chapter shows how to use Outlook code in solving a real-world programming problem. ❑ ❑ Chapter 10 creates a task management system that utilizes the new PropertyAccessor and a custom task pane to provide hierarchical task linking. Appendix A provides a summary of the important features of the Outlook 2007 object model. Important new collections, events, methods, and properties are all covered, as are common property tags used with the new PropertyAccessor object. Appendix B covers Outlook development resources, such as Microsoft and third-party Web sites, resources for Outlook programming code samples, and tools that are useful for Outlook development. It also shows you how to get help and support for Outlook development, covering Microsoft and third-party Web sites, support newsgroups, and support mailing lists. ❑ What You Need to Use This Book The Outlook VBA code samples in this book require you to have Office 2007 with Outlook 2007 installed. The VB.NET and C# code examples and projects additionally require Visual Studio 2005. The VSTO code examples and projects require VSTO 2005 SE. The sample code and projects will run on any computer with those prerequisites. For readers using Windows Vista, Visual Studio 2005 requires Service Pack 1 to be able to debug code. Conventions To help you get the most from the text and keep track of what’s happening, we’ve used a number of conventions throughout the book. Boxes like this one hold important, not-to-be forgotten information that is directly relevant to the surrounding text. Tips, hints, tricks, and asides to the current discussion are offset and placed in italics like this. As for styles in the text: ❑ ❑ ❑ We highlight new terms and important words when we introduce them. We show keyboard strokes like this: Ctrl+A. We show file names, URLs, and code within the text like this: persistence.properties. xvii
Slide 21: Introduction ❑ We present code in two different ways: In code examples we highlight new and important code with a gray background. The gray highlighting is not used for code that’s less important in the present context, or has been shown before. Source Code As you work through the examples in this book, you may choose either to type in all the code manually or to use the source code files that accompany the book. All of the source code used in this book is available for downloading at www.wrox.com. The code for the addin templates and the chapters is also available for downloading at www.slovaktech.com. Once at the site, simply locate the book’s title (either by using the Search box or by using one of the title lists), and click the Download Code link on the book’s detail page to obtain all the source code for the book. Because many books have similar titles, you may find it easiest to search by ISBN; this book’s 097804470049945. Once you download the code, just decompress it with your favorite compression tool. Alternately, you can go to the main Wrox code download page at www.wrox.com/dynamic/books/download.aspx to see the code available for this book and all other Wrox books. You can copy the VBA examples in this book into a code module in the Outlook VBA project and run them to display the outputs for the email Internet message header and the Out of Office state. To enter and run the code in VBA by copying it from the book: 1. 2. 3. 4. 5. 6. 7. Use Alt+F11 to open the VBA project. Select Insert, Module. If the Property Window isn’t visible, make it show it by selecting View, Properties Window. Place your cursor in the Name property in the Properties Window, as shown in the following figure, and select the default name text, Module 1. Change the module name to Chapter 2, and press Enter to save the change. Place your cursor in the new code module, and type the code into the project. To run the code, place your cursor in one of the procedures, and use the keyboard shortcut F5 to start code execution. The VB.NET and C# code examples require Visual Studio 2005 to run, and the VSTO code examples require VSTO 2005 SE. Errata We make every effort to ensure that there are no errors in the text or in the code. However, no one is perfect, and mistakes do occur. If you find an error in one of our books, like a spelling mistake or faulty piece of code, we would be very grateful for your feedback. By sending in errata you may save another reader hours of frustration, and at the same time you will be helping us provide even higher-quality information. xviii
Slide 22: Introduction To find the errata page for this book, go to www.wrox.com and locate the title using the Search box or one of the title lists. Then, on the book details page, click the Book Errata link. On this page, you can view all errata that has been submitted for this book and posted by Wrox editors. A complete book list, including links to each book’s errata, is also available at www.wrox.com/misc-pages/booklist.shtml. If you don’t spot “your” error on the Book Errata page, go to www.wrox.com/contact/techsupport.shtml and complete the form there to send us the error you have found. We’ll check the information and, if appropriate, post a message to the book’s errata page and fix the problem in subsequent editions of the book. p2p.wrox.com For author and peer discussion, join the P2P forums at p2p.wrox.com. The forums are a Web-based system for you to post messages relating to Wrox books and related technologies and interact with other readers and technology users. The forums offer a subscription feature to email you topics of interest of your choosing when new posts are made to the forums. Wrox authors, editors, other industry experts, and your fellow readers are present on these forums. xix
Slide 23: Introduction At http://p2p.wrox.com you will find a number of different forums that will help you not only as you read this book but also as you develop your own applications. To join the forums, just follow these steps: 1. 2. 3. 4. Go to p2p.wrox.com and click the Register link. Read the terms of use and click Agree. Complete the required information to join as well as any optional information you wish to provide and click Submit. You will receive an email with information describing how to verify your account and complete the joining process. You can read messages in the forums without joining P2P, but in order to post your own messages, you must join. Once you join, you can post new messages and respond to messages other users post. You can read messages at any time on the Web. If you would like to have new messages from a particular forum emailed to you, click the Subscribe to this Forum icon by the forum name in the forum listing. For more information about how to use the Wrox P2P, be sure to read the P2P FAQs for answers to questions about how the forum software works as well as many common questions specific to P2P and Wrox books. To read the FAQs, click the FAQ link on any P2P page. xx
Slide 24: Professional Outlook® 2007 Programming
Slide 26: An Introduction to Outlook 2007 Programming Outlook stores items such as mail, appointment, task, and contact items in tables in a hierarchically structured database. This is unlike the underlying document object model that most other Office applications use and requires a change of orientation for programmers experienced in programming applications, such as Word or Excel. For this reason, this chapter explains Outlook’s data model and introduces Outlook’s data storage and data presentation models. In this chapter, you first open the Outlook VBA project and set it up for use in creating and running macros and prototype code. Using the Outlook VBA project is often the easiest way to quickly test and prototype your Outlook code. Next, you discover the concept of a NameSpace and how to access Outlook data in folders and in individual Outlook items. This is the basis of all Outlook data access and is a building block for all Outlook programming. You next see how Outlook stores its data and how to access that data. In Outlook 2007, you can now access data either with the traditional Folders and Items collections or with the new Stores collection and Table object. The new members of the Outlook object model are explained in Chapter 2. Finally, this chapter discusses Inspectors—the windows that display items such as emails or appointments, as well as Explorers— the windows that display folders. Working with these collections is critical for any Outlook program that works with the Outlook display. Setting Up Outlook VBA Outlook VBA is a very convenient way of testing and prototyping your code. You can quickly write procedures to test various elements in the Outlook object model, and in VBA code you have an intrinsic Outlook Application object that makes it easy to work with Outlook items. Instead of writing your code in a Component Object Model (COM) addin to test something, it’s far easier to quickly test in a macro and then place the tested code in your COM addin. In fact, it’s even easier to test Outlook forms code, which uses VBScript, in Outlook VBA and then convert the VBA code to VBScript than it is to test the code in an Outlook form.
Slide 27: Chapter 1: An Introduction to Outlook 2007 Programming Setting Up Macro Security The first thing when using Outlook VBA is to set up your macro security so that your code will run. By default, Outlook’s macro security is set to high, which doesn’t run code not signed with a code-signing certificate and which warns you when signed code is present. At a minimum, you should set your security to prompt you when macro code is present. Never set your security to perform no security checks; that’s a very dangerous setting that leaves you with no security at all. To set security, do the following: 1. Select Tools ➪ Macro ➪ Security to open the Security dialog. The Security dialog, shown in Figure 1-1, appears. Figure 1-1 2. Set your security: ❑ ❑ Warnings for all macros: Use this if you don’t intend to sign your Outlook VBA project. Warnings for signed macros; all unsigned macros are disabled: If you intend to sign your VBA project, it’s recommended that you use this security setting. 3. Click the OK button to save your changes to the macro security and then exit and restart Outlook to apply the new security setting. After setting up your macro security, you can open the Outlook VBA project by using the keyboard shortcut Alt+F11. 2
Slide 28: Chapter 1: An Introduction to Outlook 2007 Programming Creating a Code-Signing Certificate Unlike other Office applications such as Word or Excel, Outlook stores its macros globally and only has one VBA project that you can use. Outlook’s VBA project is always named VBAProject.OTM, and signing this code project provides a higher degree of security than just enabling warnings for all macros. Even if you don’t have a standard code-signing certificate from a trusted authority, such as VeriSign or Thawte, you can use the certificate-generating software included with Office 2007, Selfcert.exe, to create a personal signing certificate. You shouldn’t use this certificate to distribute code, because it doesn’t derive from a trusted authority, but you can use it for your own code on your development computer. To create a code-signing certificate, follow these steps: 1. Navigate to the folder where your Office 2007 applications are installed, usually C:\Program Files\Microsoft Office\Office12 for Office 2007, and run the Selfcert.exe program. Running this application results in the dialog box shown in Figure 1-2. Figure 1.2 2. 3. Enter a name for your certificate—just use your own name—and press Enter to create your personal code-signing certificate. Now open the Outlook VBA project using Alt+F11 in Outlook and select Tools ➪ Digital Signature to open the dialog shown in Figure 1-3. In this dialog, click the Choose button and select the certificate you created using Selfcert.exe, then click OK twice to choose your certificate and sign your code project with that certificate. Figure 1-3 3
Slide 29: Chapter 1: An Introduction to Outlook 2007 Programming 4. 5. Now select File ➪ Save VBAProject.OTM to save your VBA project; then exit and restart Outlook. If you are prompted again to save your VBA project when Outlook is closing, save the project again. When you open your VBA project using Alt+F11, you are prompted to enable your macros in the dialog shown in Figure 1-4. You can use this dialog to trust your certificate, which prevents this dialog from appearing again, or you can just enable the macros for that Outlook session. It’s recommended to trust your certificate so you don’t get prompted to trust your VBA code every time you want to run a macro or go into the Outlook VBA project. Figure 1-4 6. Click the Trust All Documents from This Publisher button to trust your code-signing certificate and add it to the trusted publishers list. If you open the Trust Center dialog shown in Figure 1-1 again and click the Trusted Publishers area, you will now see your code-signing certificate listed as a trusted publisher. Now you won’t get the security prompts, and your VBA code will run without problems. Reviewing the VBA Editor Interface and Options Take a few minutes to explore the menus in the Outlook VBA interface and click Tools ➪ Options to set up the VBA editor options so that the settings match what you want: ❑ The Require Variable Declaration option: Checking this checkbox is recommended. It inserts an Option Explicit statement in every module you add to your project. Declaring your variables before using them is a valuable tool in making sure that you are typing your variables and objects correctly and is good programming practice. The Object Browser: Before leaving the Outlook VBA project use the keyboard shortcut F2 to open the Object Browser, and spend a few minutes reviewing the properties, methods and events in the Outlook object model. The Object Browser is the most useful tool you have when writing Outlook code. It shows what is available in the Outlook object model and provides prototypes of all the Outlook properties, methods, and events that show how they are used. Clicking F1 to call Help when any entry in the Object Browser is selected brings up context-sensitive Help about that Outlook object, usually with some sample code to show how to use and access that object. ❑ 4
Slide 30: Chapter 1: An Introduction to Outlook 2007 Programming Understanding Outlook When you start Outlook, you are logged into an Outlook session. An Outlook session starts when Outlook is started and ends when you close Outlook. The data and properties that you have available to you in this session are set by the Outlook profile that logged into that Outlook session. Each Outlook profile consists of a data store, email accounts, and other settings that define the Outlook environment. Most users have only one default Outlook profile, but Outlook allows you to set up as many profiles as you want to define different environments in which to run Outlook. Outlook can only have one session open at a time and can use the properties from only one Outlook profile at a time. To switch to a different Outlook profile, you must close and reopen Outlook. Figure 1-5 shows the hierarchical structure used to organize access to Outlook data. The Outlook Application opens one session, also known as a NameSpace. The session has a Stores collection, consisting of all open stores in that session. One or more stores may be opened in a session. Each store has a collection of folders, and each folder has a collection of items that make up the data in that folder. Outlook Application NameSpace Stores Store Store Store Folder Folder Folder Items Item Figure 1-5 5
Slide 31: Chapter 1: An Introduction to Outlook 2007 Programming Outlook Profiles and Data Store Each Outlook profile has access to selected data stores, which can be PST files, Exchange mailboxes, Exchange public folders, or special stores known as custom store providers. A profile also has certain email accounts set up, and send/receive groups, which control how certain Outlook features behave, such as sending emails on a deferred basis or sending meeting invitations. Regardless of the profile or profile properties, the Outlook session always opens into a NameSpace, which is always “MAPI”: Dim oNS As Outlook.NameSpace Set oNS = Application.GetNameSpace(“MAPI”) All of the code snippets in this chapter use VBA, Outlook’s macro language. VB.NET and C# code samples in the book show similar code in those languages. MAPI stands for Messaging Application Programming Interface, and Outlook uses MAPI protocols for all of its data accesses, and the Outlook data stores are designed around the MAPI storage interface. Outlook wraps most of the MAPI protocols and data storage in the Outlook object model, so most of the time you don’t have to deal directly with the complexities of MAPI. Outlook Sessions and Data Stores In any Outlook session, the available data stores, such as PST files, are all contained in the Stores collection of the NameSpace object. Each Store object in the Stores collection provides access to the top level of that data store. You can also access these stores at a lower level from the NameSpace object’s Folders collection. Stores are new to the Outlook object model in Outlook 2007. Each store has its own Folders collection, and each Folder object in those collections can contain its own Folders collections (subfolders). The user interface represents these collections and folders as Outlook folders and subfolders. The Evolution of the MAPIFolder object In previous versions of the Outlook object model, the Folder object was called the MAPIFolder object. This alias is still accepted in Outlook code, so the forward compatibility of existing code isn’t compromised. The new properties and methods for the Folder object, such as the GetTable() method used to get a Table object for a folder, are also available for the MAPIFolder object, although only for Outlook 2007. The new events for the Folder object are not added to the MAPIFolder object. If you enhance existing code to use the new Folder events, make sure to convert all declarations of MAPIFolder objects into Folder declarations. Outlook Folders Each folder in Outlook is dedicated to containing certain types of items, such as emails, tasks, contacts, or appointments. Folders contain Items collections to hold items that are compatible with the type of 6
Slide 32: Chapter 1: An Introduction to Outlook 2007 Programming folder (refer to Figure 1-5). There are also special folders such as Inbox and Deleted Items that can hold items of many different types. The following VBA code snippet illustrates how to use code to access one item from the Outlook Sent Items folder using Outlook VBA code: Dim Dim Dim Dim Set Set Set Set oNS As Outlook.NameSpace oFolder As Outlook.Folder ‘note: this is new for Outlook 2007 colItems As Outlook.Items oMail As Outlook.MailItem oNS = Application.GetNameSpace(“MAPI”) oFolder = oNS.GetDefaultFolder(olFolderSentMail) colItems = oFolder.Items oMail = colItems.Item(1) This code uses the intrinsic Application object that always represents Outlook.Application when running in the Outlook VBA project, or in custom form code. In other contexts, such as VBA code running in a Word macro, an Outlook.Application object needs to be declared and instantiated: Dim oOL As Outlook.Application Set oOL = CreateObject(“Outlook.Application”) Please note the following regarding this code: ❑ The code instantiates a NameSpace object and uses the GetDefaultFolder() method to retrieve a reference to the Sent Items folder. This folder is declared as a Folder object, new for Outlook 2007. The Folder object is derived from the MAPIFolder object and adds additional methods, properties and events to that object. The code then gets the Items collection of the Sent Items folder and returns a reference to one item from the Items collection. ❑ ❑ This code is typical of how you access an Outlook item from an Outlook data store, navigating the storage and interface hierarchy to access that item. This code assumes that the first item in Sent Items is an email item, which it may not be because email folders can also hold Post items. Many Outlook objects such as Application, Inspector, and Explorer have a Session property that returns the NameSpace object and exposes all the properties, methods and events of the NameSpace object. The Session object can be used as an alias for the NameSpace object. If you use the Session object make sure you set a NameSpace object using the GetNameSpace(MAPI) method once in your code before using the Session object. The Session object isn’t fully instantiated until you log in to the NameSpace object at least once, particularly when you start Outlook using automation code. 7
Slide 33: Chapter 1: An Introduction to Outlook 2007 Programming Outlook Items Each Outlook item contains many properties that define the data for that item. Default properties are present in every item and are always defined for an item, such as Subject or Body (the note text in an item). Users can also add properties to items either from code or the user interface. These UserProperties are also called named properties and play a prominent role in most advanced Outlook development, often being used to store application-specific data in items. The following example uses the oMail object instantiated in the previous example: Dim colProps As Outlook.UserProperties Dim oProp As Outlook.UserProperty Dim sSubject As String sSubject = oMail.Subject If InStr(1, sSubject, “support”, vbTextCompare) > 0 Then Set colProps = oMail.UserProperties Set oProp = colProps.Item(“Processed Date”) If oProp Is Nothing Then ‘if Nothing add the user property Set oProp = colProps.Add(“Processed Date”, olDateTime) End If oProp.Value = #April 21, 2007# ‘set a date this item was processed oMail.Save ‘save the change to the MailItem End If This example tests to see if the Subject of a mail item contains the case-insensitive string “support”. If so, it tries to get an existing UserProperty named “Processed Date” from a mail item. If that property doesn’t exist, the code adds it to the item. The Value property of “Processed Date” is set, and then the mail item is saved. You can add data in a UserProperty to a view of an Outlook folder, so it’s visible to users. Outlook Data Storage Programming Outlook is different from programming most other Microsoft Office applications in that it’s not centered on documents but rather on individual items stored in databases. Each Outlook item, whether it’s an appointment or an email, is really a row in a database with the database columns being individual properties, such as Subject. Outlook disguises the underlying data storage with collections of folders and items, but you still access a database when you work with Outlook data. Outlook data is stored in PST (Personal Storage) files for users not working with Exchange server, and for archive and offloaded data even for Exchange server users. Other Outlook data stores are OST (Offline Storage) files used in offline and cached mode by Exchange server users and Exchange mailboxes. What these data stores have in common are storage organization and the use of a derivative of the JET database used for Access. Using Open DataBase Connectivity (ODBC) database drivers Outlook items can be accessed as recordsets, although only a subset of the properties in an item is available as columns in the recordset. Outlook data is organized into Stores, Folders, and Items. Each store, such as a PST file, has a one-tomany relationship to folders, including default folders such as Inbox and Calendar, and nondefault folders 8
Slide 34: Chapter 1: An Introduction to Outlook 2007 Programming that the user creates. Each Folder object has a Folders collection, which can contain many subfolders, with each of those subfolders able to contain subfolders of its own. Folders have one-to-many relationships with items, such as emails and contacts. Each item has many different properties stored in its row in the folder item table, including properties common to all items, such as Subject, and special properties related to certain items such as the start date and time of an appointment. Accessing Data Stores Using NameSpace When you access an Outlook data store, you do so in one of two ways. The first and familiar way for Outlook programmers is from the NameSpace.Folders collection, the other, new way is to use the NameSpace.Stores collection. The difference is in the returned object and where you end up in the store hierarchy. When you use the NameSpace.Folders collection, or one of the methods that retrieves a folder from the NameSpace object, such as GetDefaultFolder() or GetFolderFromID(), you get a Folder (MAPIFolder) object. If you use NameSpace.Folders.Item(1), you get a Folder that is called the RootFolder or Top of Information Store. This is usually shown as something like “Personal Folders” or “Mailbox - Ken Slovak.” The following VBA examples show how to retrieve a Folder object using some of the various NameSpace methods: Dim oNS As Outlook.NameSpace Dim oFolder As Outlook.Folder ‘note: this is new for Outlook 2007 Set oNS = Application.GetNameSpace(“MAPI”) ‘Get a default Outlook folder Set oFolder = oNS.GetDefaultFolder(olFolderCalendar) ‘get Calendar ‘Another way: Set oFolder = oNS.GetFolderFromID(strFolderID) ‘using a string EntryID ‘Another way: Set oFolder = oNS.Folders.Item(“Personal Folders”) ‘get RootFolder The disadvantage of these methods is they can only take you so high in the Outlook folders hierarchy, to the Top of Information Store or RootFolder. There are other interesting Outlook folders, however, that aren’t visible to the user but are important to programmers, such as the Reminders and Common Views folders, as well as Search Folders and the new To-Do Bar task list folder. Using the new Stores collection, you can access these hidden folders, as well as properties of the store itself, such as the Out of Office state and the support mask that indicates what types of operations the store supports. Accessing Data with Tables Some of the new features in the Outlook 2007 object model relate to tables, which provide direct readonly access to an Outlook table, such as an Items collection of all items in a folder. These tables provide much faster access to your Outlook data than using other methods, such as a iterating through a folder’s Items collection or using a filter on an Items collection. The disadvantage of table operations is that the data is read-only, so if you need to change your returned data, you must include columns with information that lets you retrieve individual items corresponding to the rows that need to be changed. 9
Slide 35: Chapter 1: An Introduction to Outlook 2007 Programming To retrieve individual items from the Outlook database, whether from tables or Items collections, you make use of one or both of the GUIDs (Globally Unique Identifiers) that uniquely identify all Outlook items in the Outlook data storage. The first and most important GUID is named EntryID. This GUID uniquely identifies any Outlook item in an Outlook data store, such as a PST file or Exchange mailbox. The other important GUID is StoreID, which is unique to any Outlook data store and which you can use in combination with EntryID to retrieve any Outlook item stored in any opened Outlook data store: Set oMail = oNS.GetItemFromID(strEntryID, strStoreID) where strEntryID and strStoreID are string variables that are storing specific GUIDs for an Outlook item and data store. Outlook Data Display Now that you know how Outlook stores and accesses data, you need to understand how Outlook displays its data. Outlook has two primary display collections, the Explorers and Inspectors collections. Outlook also has other user interface objects, such as the familiar menus and toolbars that are composed of the CommandBars collection, the new Ribbon displayed in open items such as emails, and various Panes, such as the Navigation and Reading (preview) Panes. You learn how to use all those interface elements and to create or modify them in Chapter 7, but for now let’s concentrate on the Explorers and Inspectors collections. ❑ The Explorers collection contains all open folder views, such as a view of the Inbox or Calendar. Each open folder view is an Explorer object and has a CurrentFolder object, which is the folder whose data is currently being displayed in the Explorer. The currently active Explorer object is also available as the ActiveExplorer object. When you open Outlook as a user, one Explorer object is opened. You can add additional Explorers to the Explorers collection by right-clicking a folder in the Navigation Pane and selecting Open in New Window. When you open Outlook using code, there are no open Explorers unless you explicitly open an Explorer to display the contents of a folder. The Inspectors collection contains all open Outlook items. Each time you open an Outlook item, such as an email, a contact, or an appointment item, you are adding an Inspector to the Inspectors collection. Logically, the currently active Inspector object is available as the ActiveInspector object. ❑ The distinction between Inspectors and Explorers is fundamental to Outlook programming, and they are often confused by new Outlook programmers. If you want to access a folder being displayed in Outlook, use the Explorers collection. If you want to access open Outlook items, use the Inspectors collection. You cannot access the display of a folder without using an Explorer, and you can’t access any open Outlook items without using an Inspector. To change the subject of a newly opened email, use the following code to access the ActiveInspector and the item being displayed in the Inspector: Dim oMail As Outlook.MailItem Dim oInsp As Outlook.Inspector 10
Slide 36: Chapter 1: An Introduction to Outlook 2007 Programming Set oInsp = Application.ActiveInspector If oInsp.CurrentItem.Class = olMail Then ‘ check for mail item first Set oMail = oInsp.CurrentItem oMail.Subject = “Test Message” oMail.Save ‘save the change Else MsgBox “This is not a mail item” End If This code first checks the ActiveInspector to make sure that the currently displayed item in the Inspector is a mail item, and if it is it changes the Subject to “Test Message.” If the item is not a mail item, it displays a message box describing the problem. The following code shows how to use the current ActiveExplorer object to first display the default Contacts folder and then change the view of that folder to the Business Cards view: Dim Dim Dim Dim oNS As Outlook.NameSpace oExpl As Outlook.Explorer oFolder As Outlook.Folder oView As Outlook.View Set oNS = Application.GetNamespace(“MAPI”) Set oExpl = Application.ActiveExplorer Set oFolder = oNS.GetDefaultFolder(olFolderContacts) Set oExpl.CurrentFolder = oFolder oExpl.CurrentView = “Business Cards” The code gets the default Contacts folder using the GetDefaultFolder method, using the olFolderContacts constant, and then sets the CurrentFolder property object of the Explorer to the Contacts folder. The Explorer’s CurrentView property is then set to “Business Cards.” Summar y In this chapter, you learned how to configure the Outlook VBA project, so you can use it without warning prompts and so your VBA code will run. You also learned: ❑ ❑ ❑ How to access Outlook items using the Outlook object model. How Outlook organizes its data storage. The differences between Explorers and Inspectors and how they are used to display Outlook objects. In the next chapter, you learn about some of the most important new features of the Outlook object model and how they are used to display Outlook objects. 11
Slide 38: What’s New in Outlook 2007 This chapter explains the most important new collections, objects, methods, properties, and events in the Outlook object model. So many things were added to the Outlook object model in Outlook 2007 that the object model doubled its size, making it hard to select the most important new features. At the end of the Outlook 2000 beta, the Outlook MVPs (Microsoft Most Valuable Professionals) involved in Outlook development wrote a whitepaper listing items they wanted added to the Outlook object model so that development could be done using only Outlook code, without dipping into other APIs. Thanks to the splendid work of the Outlook team at Microsoft, spearheaded by Randy Byrne and Ryan Gregg, we finally have almost everything we asked for so many years ago. The selections for this chapter are the ones I consider the most important, based on many years as an Outlook developer. All new Outlook 2007 features are covered in this book even if they aren’t in this chapter: ❑ The unified object model: The Outlook object model now has many of the properties and methods of other APIs (Application Programming Interfaces), such as CDO 1.21 (Collaboration Data Objects), making Outlook 2007 a complete development platform. For most programming tasks, Outlook programmers now don’t have to master additional APIs to perform basic Outlook programming functions. This chapter explains important new additions to the Outlook object model, such as the PropertyAccessor, Stores, and StorageItems, user interface objects, tables, and accounts. You also learn about the limitations and bugs in some of the new additions to the Outlook object model. Improving your performance: Another part of this chapter explains how to use tables to increase the often slow performance of Outlook in accessing filtered groups and collections of items. Using tables for such accesses instead of filtered or restricted Items collections can often yield an order of magnitude performance improvement. Increased security: You learn how the Outlook object model security guard treats trusted and untrusted Outlook code and how to ensure that code in Outlook COM addins is trusted by Outlook. ❑ ❑
Slide 39: Chapter 2: What’s New in Outlook 2007 ❑ DASL: You discover DASL syntax and why mastering this syntax is so important in Outlook 2007 programming. DASL is the search syntax (DAV Searching and Locating) used in WebDAV (Web Distributed Authoring and Versioning) and also for Exchange and Outlook access based on schemas defined by Microsoft. The more familiar JET syntax for accessing Outlook items is also explained and contrasted with using DASL syntax. The Unified Object Model This section and the following sections in this chapter introduce the most important new things in the Outlook object model. Appendix A, which discusses the Outlook object model, contains a complete list of the new collections, objects, properties, methods, and events added to the Outlook 2007 object model. The Outlook 2007 developer Help file lists the complete Outlook object model reference, with many extensive code samples illustrating the use of the object model components. It also has sections on what was added to the Outlook object model in each major release since Outlook 97 was released. There are sections in Help that list changes since Outlook 97, Outlook 2000, Outlook 2002, and Outlook 2003. There is also a section in Help about the developer issues that can be expected when upgrading code solutions to Outlook 2007. More than ever, the Outlook Help file really is helpful. The PropertyAccessor Even with all the added properties in Outlook items, there are still some that aren’t exposed in the Outlook object model (OOM). For example, the Internet message headers aren’t available as a property in an Outlook MailItem. object. The headers are available to the new Rules objects, but for some reason aren’t exposed in the OOM. There are also properties that are quite useful, but are undocumented or poorly documented and not exposed in the OOM, such as the Out of Office state for Exchange users. For years Outlook developers used alternate APIs such as CDO 1.21 (CDO) to access Fields collections that provided access to properties not exposed in the OOM for items, folders, and stores. The new PropertyAccessor object gives Outlook developers parity with developers using other APIs. You now can access any property, if you know the property tag, anywhere in Outlook using the PropertyAccessor. As you may have discovered in Chapter 1, Outlook stores data in rows and columns in a database. These columns include not only the properties exposed in the OOM for items, folders, and stores but also all the properties defined in MAPI for those objects. The Outlook defined columns are known to programmers as properties, or named MAPI properties for properties that are added to an item only when data is stored in the property, such as BusinessAddressStreet. User defined properties are known as UserProperties or Named Properties. UserProperties are visible to users, while named properties are not visible to the user. All these types of properties are accessible to the PropertyAccessor. Outlook properties and named MAPI properties can’t be deleted by using the PropertyAccessor. UserProperties and Named Properties can be deleted using the PropertyAccessor. The justification for this is to protect Outlook from essential properties being deleted and causing data loss or crashes, although I can think of many ways to cause either or both with existing methods. The PropertyAccessor object provides access to properties that aren’t exposed to the Outlook object model, so it must use different syntax than the usual strSubject = oItem.Subject. The syntax is similar to the syntax for the AdvancedSearch method, a DASL schema syntax that resembles an 14
Slide 40: Chapter 2: What’s New in Outlook 2007 Internet URL. The arguments passed to the PropertyAccessor methods refer to the DASL property identifiers as SchemaName. The following methods are provided to get, set, and delete one or multiple properties at a time: ❑ ❑ ❑ GetProperty(SchemaName), GetProperties(SchemaNames) SetProperty(SchemaName, Value), SetProperties(SchemaNames, Values) DeleteProperty(SchemaName), DeleteProperties(SchemaNames) To create a new property using the PropertyAccessor, use the SetProperty method and make sure to save the Outlook item so that the new property is saved. The PropertyAccessor object also provides helper methods for date/time and binary array conversion: ❑ ❑ LocalTimeToUTC, UTCToLocalTime BinaryToString, StringToBinary One thing to be aware of when working with the time conversion methods is that Outlook 2007 rounds the values to convert to the nearest minute; it doesn’t use the seconds part of any times. Therefore, any time comparisons between property values need to take this rounding into account. One way to do that is to test for a difference of 1 minute or less as an approximation of time equality. The PropertyAccessor object accepts four types of SchemaName syntax to specify the property you want to work with: ❑ Property tag syntax uses the string http://schemas.microsoft.com/mapi/proptag/0x followed by the property tag of the property. An example is http://schemas.microsoft.com/ mapi/proptag/0x007D001E for an email’s Internet mail headers. This syntax for SchemaName is used for Outlook properties. Property ID syntax uses the string http://schemas.microsoft.com/mapi/id/ followed by a GUID namespace for the item and property types, followed by a property ID, followed by a string for the data type of the property. An example is http://schemas.microsoft.com/mapi/ id/{00062004-0000-0000-C000-000000000046}/8082001E for a contact’s Email1AddressType property. This syntax is used for MAPI Named Properties and UserProperties. Named Property syntax uses the string http://schemas.microsoft.com/mapi/string/ followed by a GUID namespace for the item and property types, followed by a property name. An example is http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/ keywords, which is the Categories field in Outlook, containing the category names. This syntax is used for Named Properties and some MAPI named properties. This is the syntax you use most often to create named properties in your code. Office document syntax uses the string urn:schemas-microsoft-com:office:office# for Office DocumentItem support. In this syntax, the name of the property is concatenated to the end of the property tag string. An example is urn:schemas-microsoft-com:office: office#author for the author of a document. ❑ ❑ ❑ The following sections describe all four PropertyAccessor syntax types. 15
Slide 41: Chapter 2: What’s New in Outlook 2007 PropertyAccessor Syntax 1 The first example of using the PropertyAccessor in Listing 2.1 uses the Property Tag syntax, http://schemas.microsoft.com/mapi/proptag/0x, concatenated with a property tag string, in this case 007D001E. The resulting string, http://schemas.microsoft.com/mapi/proptag/0x007D001E is the way to retrieve the Internet mail headers from an email item. This example assumes the item selected in the Inbox is an email item that was received using an Internet mail protocol. It doesn’t provide any error handling if this is not true. Listing 2.1 Public Sub ShowMailHeaders() Dim oNS As Outlook.NameSpace Dim oInbox As Outlook.Folder ‘using new Folder object Dim colItems As Outlook.Items Dim oMail As Outlook.MailItem Dim oPropAccessor As Outlook.PropertyAccessor Dim strHeaders As String Const PR_MAIL_HEADER_TAG As String = _ “http://schemas.microsoft.com/mapi/proptag/0x007D001E” Set oNS = Application.GetNameSpace(“MAPI”) Set oInbox = oNS.GetDefaultFolder(olFolderInbox) Set colItems = oInbox.Items Set oMail = colItems.Item(1) Set oPropAccessor = oMail.PropertyAccessor ‘PropertyAccessor property tags are always strings strHeaders = oPropAccessor.GetProperty(PR_MAIL_HEADER_TAG) MsgBox “Mail Headers: “ & vbCRLF & strHeaders End Sub You learn more about DASL syntax and the various schemas used for Outlook properties later in this chapter in the section “The Importance of DASL.” You also learn about using a different type of DASL filter, the Content Indexer filter in that section of this chapter. The most common DASL property tags for Outlook items are listed in Appendix A and resources for the various schemas used for Outlook and tools to work with Outlook properties are discussed in Appendix B. For the previous code, notice the following: ❑ First, the code gets the NameSpace object and uses the GetDefaultFolder method to retrieve the Inbox as a Folder object. The Items collection of the Folder contains all visible items in the folder The code assumes that the first item in the Items collection is an email and was sent by Internet mail and retrieves it as an Outlook MailItem. ❑ 16
Slide 42: Chapter 2: What’s New in Outlook 2007 ❑ The PropertyAccessor retrieves the value of the Internet headers, which is a string, and displays the headers in a message box. Standard Outlook properties use standard property tags, for example Subject is always 0x0037001E. You can always retrieve any standard property value from Outlook, even if the property is available using other, different syntax by using the http://schemas.microsoft.com/mapi/proptag/0x syntax and concatenating the property tag value as a string to the schema. An exception to this is the Body property of an item, which is blocked to the PropertyAccessor. This limitation is due to the potentially extremely large size of the body of an item. All properties have a type, such as Boolean or String8 (ANSI 8-bit string), as well as a property tag. The property tag is a 32.bit integer that is usually expressed as a hexadecimal number (a number in Base16 notation). Property tags have different ranges, with properties in the 0x80000000 range known as named properties. This range contains user-defined as well as Outlook-defined properties that aren’t always present on an item, such as BusinessAddressState for contact items. The notation 0x80000000 is hexadecimal notation for the signed value -2147483648. There are different ways of expressing hexadecimal numbers, such as the Visual Basic format that precedes the number with &H. This book uses the 0x notation familiar to C++ and C# programmer. Hexadecimal notation is a convenient way of expressing numbers for programmers and is used extensively in this book. The 0x notation is used because that is the notation used for PropertyAccessor property tags. The biggest limitation of the PropertyAccessor is a limitation on how much data you can read from a string or binary property. This limitation applies to user properties, properties whose property tags are above 0x80000000. In most cases, you can’t read any string or binary property that is larger than 4088 bytes. The exceptions to this are online Exchange mailboxes (not in cached mode) and Exchange public folder stores. Those Exchange stores have a limit of 16,372 bytes for read operations. Reading properties larger than that size may return an out of memory error. A PropertyAcccessor can write much more data than it can read back, potentially creating a problem. Both CDO and Redemption (a third-party COM wrapper for Extended MAPI functionality) can be used to avoid this problem. CDO and Redemption fall back to retrieving the property data for large properties as streams, working around a MAPI limitation. PropertyAccessor does not currently implement this workaround, so it has the 4 KB read limitation on string and binary properties. The value of the property tag for the Internet headers is below 0x80000000, so headers larger than 4 KB can be retrieved without errors. The second example of using Property Tag syntax gets the user’s Out of Office state. The code to find the Out of Office state first gets the default Outlook store, and if the store is an Exchange mailbox, then retrieves the Boolean value of the Out of Office property. Listing 2.2 defines a constant string for the Out of Office state using the property name used in the MAPI header files, PR_OOF_STATE. Outlook programmers often use this type of property name to retain meaning between Outlook and the underlying MAPI. Listing 2.2 Public Sub ShowOutOfOfficeState() Dim oNS As Outlook.NameSpace (continued) 17
Slide 43: Chapter 2: What’s New in Outlook 2007 Listing 2.2 (continued) Dim Dim Dim Dim colStores As Outlook.Stores oStore As Outlook.Store oPropAccessor As Outlook.PropertyAccessor blnOOF_State As Boolean Const PR_OOF_STATE As String = _ “http://schemas.microsoft.com/mapi/proptag/0x661D000B” blnOOF_State = False Set oNS = Application.GetNameSpace(“MAPI”) Set colStores = oNS.Stores For Each oStore In colStores If oStore.ExchangeStoreType = _ OlExchangeStoreType.olPrimaryExchangeMailbox Then Set oPropAccessor = oStore.PropertyAccessor ‘PropertyAccessor property tags are always strings blnOOF_State = _ oPropAccessor.GetProperty(PR_OOF_STATE) Exit For End If Next MsgBox “Out of office is “ & blnOOF_State End Sub PropertyAccessor Syntax 2 In Property ID syntax, you create property tags for named properties using a schema, GUID (a globally unique string) defining the property usage, a property ID and the property type. These properties are added to an item either by Outlook or by code when data is added to the property. Examples of properties added to items only when data is added to the properties are Private and Email1AddressType. You use different GUIDs for different classes of property types with Property ID syntax, for example, properties for tasks or contacts. ❑ ❑ ❑ ❑ The schema for most named properties is http://schemas.microsoft.com/mapi/id/. The GUID for Email1AddressType is the contact GUID, {00062004-0000-0000-C000000000000046}. The property ID for Email1AddressType is 8082. Email1AddressType is a string, so to retrieve the ANSI display name a property type string, use 001E. To retrieve the Unicode display name string, use 001F as the property type string. The complete SchemaName for Email1AddressType is therefore: http://schemas.microsoft.com/mapi/id/“ + “{00062004-0000-0000-C000-000000000046}“ + “/8082” + “001E” 18
Slide 44: Chapter 2: What’s New in Outlook 2007 The following code blocks for variations of Basic and C# show some common declarations of constants for property types, GUIDs and schemas. Additional schema declarations are shown in the Importance of DASL section of this chapter. The declarations follow MAPI style, where the PT prefix indicates a property type. VB.NET, VBA, VB 6 ‘ Property type constants Const PT_STRING8 As String = “001E” Const PT_MV_STRING8 As String = “101E” Const PT_UNICODE As String = “001F” Const PT_MV_UNICODE As String = “101F” Const PT_BINARY As String = “0102” Const PT_MV_BINARY As String = “1102” Const PT_BOOLEAN As String = “000B” Const PT_SYSTIME As String = “0040” Const PT_LONG As String = “0003” ‘ 32 bits Const PT_DOUBLE As String = “0005” ‘8 byte integer (64 bits) - this is not a usable type for VB 6 or VBA ‘only use with VB.NET Const PT_I8 As String = “0014” ‘ GUIDs for various named property types Const MAPI_NAMED_PROP As String = _ “{00020329-0000-0000-C000-000000000046}“ Const MAPI_NAMED_TASK_PROP As String = _ “{00062003-0000-0000-C000-000000000046}“ Const MAPI_NAMED_APPT_PROP As String = _ “{00062002-0000-0000-C000-000000000046}“ Const MAPI_NAMED_CONTACT_PROP As String = _ “{00062004-0000-0000-C000-000000000046}“ Const MAPI_NAMED_MAIL_PROP As String = _ “{00062008-0000-0000-C000-000000000046}“ ‘ Property Schemas Const PROPERTY_TAG_SCHEMA As String = _ “http://schemas.microsoft.com/mapi/proptag/0x” Const PROPERTY_ID_SCHEMA As String = _ “http://schemas.microsoft.com/mapi/id/“ C# // Property type constants const string PT_STRING8 = “001E”; const string PT_MV_STRING8 = “101E”; const string PT_UNICODE = “001F”; const string PT_MV_UNICODE = “101F”; const string PT_BINARY = “0102”; const string PT_MV_BINARY = “1102”; const string PT_BOOLEAN = “000B”; const string PT_SYSTIME = “0040”; const string PT_LONG = “0003”; // 32 bits const string PT_DOUBLE = “0005”; const string PT_I8 = “0014”; // 64 bits (continued) 19
Slide 45: Chapter 2: What’s New in Outlook 2007 C# (continued) // GUIDs for const string const string const string const string const string various named property types MAPI_NAMED_PROP = “{00020329-0000-0000-C000-000000000046}“; MAPI_NAMED_TASK_PROP = “{00062003-0000-0000-C000-000000000046}“; MAPI_NAMED_APPT_PROP = “{00062002-0000-0000-C000-000000000046}“; MAPI_NAMED_CONTACT_PROP = “{00062004-0000-0000-C000-000000000046}“; MAPI_NAMED_MAIL_PROP = “{00062008-0000-0000-C000-000000000046}“; // Property Schemas const string PROPERTY_TAG_SCHEMA = “http://schemas.microsoft.com/mapi/proptag/0x”; const string PROPERTY_ID_SCHEMA = “http://schemas.microsoft.com/mapi/id/“; An easy way to find the DASL definition of most Outlook properties is to use the filter definition window for Outlook views. To do so: 1. 2. 3. 4. 5. 6. 7. 8. Select View ➪ Current View ➪ Define Views to open the Custom View Organizer. Click the New button to open the Create a New View dialog, and click OK to accept the defaults. In the Customize View: New View dialog, click the Filter button. Select the Advanced tab, and use the Field button to select the property in which you are interested. For a string value, such as Email1AddressType, select “is empty” in the Condition drop-down. Click the Add to List button to accept the query. Select the SQL tab, and check the Edit These Criteria Directly checkbox to enable you to view and copy the generated SchemaName for use with the PropertyAccessor. Cancel the custom view dialogs after copying the SchemaName to avoid actually creating the custom view. Figure 2.1 shows the SQL shown for the query Email1AddressType is empty, with the information needed to access the Email1AddressType property. Figure 2.1 20
Slide 46: Chapter 2: What’s New in Outlook 2007 Using the SQL tab in the custom view designer is also a handy way to learn the SQL syntax used with AdvancedSearch, and in other places in Outlook. The code to retrieve the string value of the Email1AddressType from an already instantiated PropertyAccessor object for a contact item is: ‘ To retrieve the Unicode string use “001F” instead of “001E” strAddyType = _ “http://schemas.microsoft.com/mapi/id/“ & _ “{00062004-0000-0000-C000-000000000046}“ & “/8082” & “001E” strAddrType = oPropertyAccessor.GetProperty(strAddyType) You add task properties to flagged items so that you can view the items in the To-Do Bar. When the item is marked complete, it’s removed from the To-Do Bar. The code to set the Complete status for a contact, where Complete is used to control viewing a flagged contact in the To-Do Bar, is: strTaskComplete = _ “http://schemas.microsoft.com/mapi/id/“ & _ “{00062003-0000-0000-C000-000000000046}“ & “/811C” & “000B” oPropertyAccessor.SetProperty(strTaskComplete, True) In this code, the task GUID {00062003-0000-0000-C000-000000000046} is concatenated with the property ID /811C and the property type value for a Boolean property, 000B. The property is set to True, which marks the associated task in the To-Do Bar complete. Make sure to save the Outlook item so that your changes are preserved. PropertyAccessor Syntax 3 The third PropertyAccessor syntax is also for named properties, but you use it for named properties that have names instead of property IDs. Named properties you create in code use this syntax, as do some Outlook properties. For example the Categories property is a multi-valued string property that uses the name Keywords instead of a property ID. The schema for these properties is http://schemas .microsoft.com/mapi/string/, followed by a GUID and then the property name. This name may not be the name used in the Outlook object model for the property, just as the Categories property is represented in the schema as Keywords. After a named property is created, you cannot change its data type and name. You must delete the property and create a new one with the changed data type or name. The SchemaName for Categories uses the GUID {00020329-0000-0000-C000-000000000046}, so the complete SchemaName for Categories is: http://schemas.microsoft.com/mapi/string/{00020329-0000-0000-C000-000000000046}/ keywords. This is a different SchemaName from that shown in the SQL tab of the custom view filter dialog, urn:schemas-microsoft-com:office:office#Keywords. Both of these work to retrieve the categories from an Outlook item, demonstrating that you can retrieve many properties in Outlook using different SchemaName values. 21
Slide 47: Chapter 2: What’s New in Outlook 2007 Categories is an array of strings, so the Outlook property is a multi-valued string property. Listing 2.3 shows code to retrieve the property value from the Outlook item as a string array, then display the list of all categories in the item displays in a message box. The code shows both SchemaName variations for the Categories property, with the urn:schemas-microsoft-com:office:office#Keywords variation commented out. Uncomment this variation and comment out the earlier strSchemaName lines to verify that both return the same results from any item selected in Outlook: Listing 2.3 Public Sub ShowCategories() Dim colSelection As Outlook.Selection Dim oExpl As Outlook.Explorer Dim oPropAccessor As Outlook.PropertyAccessor Dim aryCats() As String Dim strDisplay As String Dim i As Long Dim strSchemaName As String strSchemaName = “http://schemas.microsoft.com/mapi/string/“ strSchemaName = strSchemaName & _ “{00020329-0000-0000-C000-000000000046}“ strSchemaName = strSchemaName & “/keywords” ‘strSchemaName = “urn:schemas-microsoft-com:office:office#Keywords” Set oExpl = Application.ActiveExplorer Set colSelection = oExpl.Selection Set oPropAccessor = colSelection.Item(1).PropertyAccessor aryCats() = oPropAccessor.GetProperty(strSchemaName) For i = LBound(aryCats) To UBound(aryCats) strDisplay = strDisplay & aryCats(i) & vbCrLf Next i MsgBox “Categories are “ & strDisplay End Sub PropertyAccessor Syntax 4 The final syntax for accessing Outlook properties is used primarily for DocumentItem support. You access properties such as Comments, Title, Author, and other document properties using the schema urn:schemas-microsoft-com:office:office#, followed by the name of the property. This schema is the same shown as an alternate schema for Categories. Just substitute the name of the document property you want to access for Keywords. For example, to access Comments, the SchemaName would be urn:schemas-microsoft-com:office:office#Comments. Accessing Multiple Properties The multiple property methods of the PropertyAccessor: GetProperties, SetProperties, and DeleteProperties take variant array arguments for the SchemaNames and Values arguments. If you are creating new named properties, the property types are determined from the types of the values passed to the SetProperties method. 22
Slide 48: Chapter 2: What’s New in Outlook 2007 VB.NET and C# do not support a Variant array variable type, so for development using those languages, use an Object array. For VB.NET use Dim colArgs As Object(), and for C# use object[] colArgs;. The code in Listing 2.4 shows how to set two properties at the same time on a currently opened email item, a category and text for a follow-up flag. If any errors occur, they are returned to a variant array, which is tested to see if the array contains any errors. Listing 2.4 Public Sub Set2Properties() Dim astrProps() As Variant Dim astrValues() As Variant Dim avarErrors() As Variant Dim sProp1 As String Dim sProp2 As String Dim sVal1 As String Dim sVal2 As String Dim oInsp As Outlook.Inspector Dim oMail As Outlook.MailItem Dim oPropAccessor As Outlook.PropertyAccessor Dim i As Long Set oInsp = Application.ActiveInspector Set oMail = oInsp.CurrentItem Set oPropAccessor = oMail.PropertyAccessor sProp1 = “urn:schemas:httpmail:messageflag” sProp2 = “urn:schemas-microsoft-com:office:office#Keywords” astrProps = Array(sProp1, sProp2) sVal1 = “Please follow up” sVal2 = “Red Category” astrValues = Array(sVal1, sVal2) avarErrors = oPropAccessor.SetProperties(astrProps, astrValues) If Not (IsEmpty(avarErrors)) Then For i = LBound(avarErrors) To UBound(avarErrors) If IsError(avarErrors(i)) Then MsgBox CVErr(avarErrors(i)) End If Next i End If oMail.Save End Sub The DeleteProperties and GetProperties methods also return a variant error array, which you should test to see if any errors occurred during the method calls. You should always test each element in the returned array as shown. Even if no errors occurred, the return error array may not be empty, so each array element should be tested to see whether it’s an error. 23
Slide 49: Chapter 2: What’s New in Outlook 2007 The Date/Time and Binary/String Methods You use the remaining methods of the PropertyAccessor object to convert from Outlook and MAPI formats to formats that Outlook programmers use. Outlook stores all dates internally in UTC values, where dates and times are normalized to the setting for Greenwich Mean Time with offsets for the local time zone and daylight savings time. When dates and times are returned as properties in the Outlook object model, such as CreationTime, they are converted into local time, which is shown as the property value. The PropertyAccessor object sets and returns all date and time properties using UTC values, so methods to convert to and from UTC and local time are provided. These methods are LocalTimeToUTC and UTCToLocalTime, both of which take Date values and return converted Date values. For example, to convert the local date and time January 1, 2007 4:00 PM to UTC time, the code would use: datConvertedDate = oPropAccessor.LocalTimeToUTC(#1/1/2007 4:00 PM#) These time conversion methods round the time to be converted to the nearest minute, so the converted results are only accurate to plus 1 minute of the time that was converted. Some properties returned from the Outlook object model, such as EntryID and StoreID, are retrieved and used as strings, but are actually stored internally as binary data. Technically, these properties aren’t strings, but are really PT_BINARY properties (Property Type Binary), and they are retrieved as binary arrays. Of course, you can use the Outlook object model to retrieve an EntryID or a StoreID from a Folder object, but using a PropertyAccessor is useful even for a property such as StoreID. StoreID can be retrieved from an Outlook item. However, when using the Outlook object model you must retrieve the parent Folder object where the item exists before you can retrieve the StoreID. To convert a binary property to a string, use the BinaryToString method. To convert a string to a binary property, use the StringToBinary method. The following short code snippet shows how to retrieve the StoreID from an instantiated MailItem object as a usable string value, using the BinaryToString method: Set oPropAccessor = oMail.PropertyAccessor ‘ Schema for StoreID strSchema = “http://schemas.microsoft.com/mapi/proptag/0x0FFB0102” strStoreID = oPropAccessor.BinaryToString(oPropAccessor.GetProperty(strSchema)) Stores and Storage Items Outlook stores are the top level of Outlook storage. Examples of stores are PST files and Exchange mailboxes. Storage items are invisible items stored in Outlook folders used to contain configuration information that should remain invisible to the user. In this section, you learn about working with stores and StorageItems. Working with Stores Although stores are new to the Outlook object model, programmers have used them for many years from alternate APIs, such as CDO 1.21. A Store object is the highest level of storage in Outlook and corresponds to a PST file or Exchange mailbox or the Exchange public folders. Not only do Store objects provide access to properties such as Out of Office, explored earlier in this chapter, but they also provide other unique properties that are only available on the Store object. The store’s support mask property enables you to 24
Slide 50: Chapter 2: What’s New in Outlook 2007 learn what operations, such as Unicode storage, are available. The access level property exposes the permissions that the current user has on objects in the store, and the offline state shows the current online status of that store, as examples of various store properties. In addition to store properties, the Store object provides access to special folders, such as the Reminders folder and the To-Do Search folder that the new To-Do Bar uses and all user-created search folders, as well as storage for views and other things that are only retrieved from a Store object or its derivatives. All loaded stores are contained in the Stores collection, where you can add and remove stores as well as enumerate individual loaded stores. Among other properties and methods for Store objects in this book, you learn to work with the new GetRules method@@mda new feature that Outlook developers have requested for years, which retrieves the rules defined for that store and uses code to create and modify rules. The following code gets the Rules collection of all rules from the default Store: Dim oNS As Outlook.NameSpace Dim oStore As Outlook.Store Dim colRules As Outlook.Rules Set oNS = Application.GetNameSpace(“MAP”) Set oStore = oNS.DefaultStore Set colRules = oStore.GetRules You learn much more about working with stores and rules from code later in this chapter, and in other chapters in this book. Working with StorageItems StorageItems are standard Outlook items that are invisible to the user. This makes them valuable as repositories for storing user configurations, special program settings, and other uses where you don’t want a user to view the item and change settings in an unstructured way. For years, Outlook programmers have used APIs, such as CDO 1.21, to add messages to the HiddenMessages collection or Associated Contents collections to create hidden Outlook items. Now programmers using only the Outlook object model have this option. The following code creates a StorageItem in the Inbox to store special properties related to an application: Dim oFolder As Outlook.Folder Dim oNS As Outlook.NameSpace Dim oStorage As Outlook.StorageItem Set oNS = Application.GetNameSpace(“MAPI”) Set oFolder = oNS.GetDefaultFolder(olFolderInbox) Set oStorage = oFolder.GetStorage(“STS_MyInboxStorage”, _ OlStorageIdentifierType.olIdentifyBySubject) You can call the GetStorage method in one of three ways depending on the value of the StorageIdentifierType argument. This argument uses a member of the OlStorageIdentifier enumeration: ❑ Use OlStorageIdentifier.olIdentifyByEntryID to retrieve a StorageItem by its EntryID, the unique GUID that identifies items in each data store. If no StorageItem exists with that EntryID, an error is raised. 25
Slide 51: Chapter 2: What’s New in Outlook 2007 ❑ Use OlStorageIdentifier.olIdentifyByMessageClass to retrieve or create a StorageItem by its MessageClass. If no StorageItems exist in the folder with the specified MessageClass, one is created. If more than one StorageItem exists with the same MessageClass, the item with the latest modification time is retrieved when GetStorage is called. Use OlStorageIdentifier.olIdentifyBySubject to retrieve or create a StorageItem by its Subject. GetStorage creates a new item with the requested Subject if an item with that Subject does not already exist. If more than one StorageItem exists with the same Subject, the item with the latest modification time is retrieved when GetStorage is called. When a StorageItem is created this way the MessageClass is always IPM.Storage. ❑ After you have a StorageItem, you can set its Body property to store your settings, set individual properties on the item, and add attachments to the item, just as with any other Outlook item. Always remember to save the StorageItem to persist any change you make in the item. The following code retrieves a storage item with the message class of IPM.Note.MyStorage and sets a Boolean UserProperty named ItemSynched: Dim oProp As Outlook.UserProperty Set oStorage = oFolder.GetStorage(“IPM.Note.MyStorage “, _ OlStorageIdentifierType.olIdentifyByMessageClass) Set oProp = oStorage.UserProperties.Add(“ItemSynched, olYesNo) oProp = True oStorage.Save StorageItems as implemented by Outlook 2007 have two limitations and one major bug: ❑ ❑ ❑ You cannot create or read StorageItems in Exchange public folders. To retrieve StorageItems where multiple items exist in the same folder with identical Subject or MessageClass properties, you must use a Table object. You can’t use the GetStorage method. If you create a StorageItem in a non-mail folder, the item is created as a visible item in the Inbox. Although this is likely to be among the first bugs fixed in Outlook 2007, I recommend not using StorageItems in folders other than mail folders unless you can ensure that your users will have the required bug fix. A workaround is to store StorageItems for non-mail folders in the Inbox, using the MessageClass or Subject to make the StorageItems unique. User Interface Objects In the past, Outlook has always been weak in letting you access and work with various aspects of the user interface, such as views and the NavigationPane. Outlook 2007 finally provides access to these user interface objects in a structured object model. Views Views have been programmable in Outlook since Outlook 2002, but the method of creating a custom view in code was awkward and not fully documented. About the only way to define a custom view was to create that view in the user interface and then read the XML that defined the view. This method was prone to mistakes and did not provide full access to all the formatting you could apply to a view in the user interface. For example, you could not apply autoformatting colors or turn in-cell editing on or off. 26
Slide 52: Chapter 2: What’s New in Outlook 2007 The new view specific objects, such as TableView, TimelineView, and CardView, have properties that allow you to set in-cell editing, apply autoformatting rules, set fonts, set properties to view, and more. You can read more about creating custom views using the new view objects in Chapter 6. NavigationPane, NavigationFolders, and NavigationGroups, and Modules Before Outlook 2007, you could not program the Navigation Pane in Outlook. You could find and hack the XML file that defines the Navigation Pane, but even adding a shortcut was never a sure thing. Sometimes it worked, sometimes it didn’t, and Microsoft never supported hacking the XML file. Another shortcoming of hacking the XML file was that Outlook only read it at startup and always overwrote whatever was there when it shut down. Outlook 2007 provides an object interface to the Navigation Pane using the Explorer.NavigationPane object. You can now retrieve modules from the Navigation Pane, such as the Email and Contacts modules, retrieve or set the current module, see how many modules are displayed, and discover if the Navigation Pane is collapsed. New Navigation Pane modules cannot be created using the NavigationPane object. Each module, such as the MailModule, has methods to retrieve the NavigationGroups for that module, and you can add and remove folders from a NavigationGroup. You can also get the Navigation Pane’s Shortcuts module and add or remove folders from the shortcuts. You learn to more about working with the Navigation Pane in Chapter 6. SelectNamesDialog The SelectNamesDialog is familiar to all Outlook users as the dialog you see when you click the To button in an email. This dialog, known as the AddressBook method in CDO 1.21, has previously never been exposed to Outlook programmers. This enables you to show the dialog on demand in your code in Outlook 2007 without using the CDO AddressBook dialog. The CDO AddressBook dialog returns a CDO Recipient, which can’t be used in OOM code. To get the equivalent Outlook Recipient, you must get the ID property of the CDO Recipient and use that as the EntryID argument for the Outlook NameSpace.GetRecipientFromID method. When you display a SelectNamesDialog, it appears as a modal dialog, and on return from the dialog, you can access the selected names from the SelectNamesDialog class’s Recipients collection. The following code (Listing 2.5) returns one recipient for a select names dialog that is set up as an email selection dialog. You can also set up this dialog to resemble the ones that display for assigning a task, selecting attendees for a meeting, assigning delegates, selecting members of a distribution list, and other types of name selection dialogs. Listing 2.5 Public Sub AddRecipient() Dim oSelect As Outlook.SelectNamesDialog Dim colRecipients As Outlook.Recipients Dim oRecip As Outlook.Recipient Dim oMail As Outlook.MailItem Set oMail = Application.CreateItem(olMailItem) Set oSelect = Application.Session.GetSelectNamesDialog (continued) 27
Slide 53: Chapter 2: What’s New in Outlook 2007 Listing 2.5 (continued) With oSelect .AllowMultipleSelection = False SetDefaultDisplayMode _ OlDefaultSelectNamesDisplayMode.olDefaultMail .ForceResolution = True .Caption = “My Mail Selector Dialog” .ToLabel = “My To Selector” .NumberOfRecipientSelectors = _ OlRecipientSelectors.olShowTo .Display If .Recipients.Count = 1 Then Set oRecip = _ oMail.Recipients.Add(.Recipients.Item(1).Name) End If End With oMail.Display End Sub Accounts In past versions, the installed email accounts in an Outlook profile have never been accessible from Outlook object model code, and there was never a way to control what account was used to send out an Outlook item. However, Outlook 2007 now provides a NameSpace.Accounts collection that includes all the accounts set up in that Outlook profile. In addition, you can send mail, appointment, task, and sharing items using a specific account from code. Listing 2.6 creates a new mail item and iterates the Accounts collection to find the first POP3 account; then the code uses that account to send the email. Listing 2.6 Public Sub SendUsingPOP3() Dim oMail As Outlook.MailItem Dim colAccounts As Outlook.Accounts Dim oNS As Outlook.NameSpace Dim oAccount As Outlook.Account Dim strUser As String Dim strAddress As String Dim strAccountName As String Dim blnFound As Boolean blnFound = False Set oNS = Application.GetNameSpace(“MAPI”) 28
Slide 54: Chapter 2: What’s New in Outlook 2007 Listing 2.6 (continued) Set colAccounts = oNS.Accounts For Each oAccount In colAccounts If oAccount.AccountType = OlAccountType.olPOP3 Then strAddress = oAccount.SmtpAddress strUser = oAccount.UserName strAccountName = oAccount.DisplayName blnFound = True Exit For End If Next If blnFound Then Set oMail = Application.CreateItem(olMailItem) oMail.Subject = “Sent using: “ & strAccountName oMail.Body = “Sent by “ & strUser & vbCRLF & _ “Sent using the “ & strAddress & “ SMTP address.” oMail.Recipients.Add(“test@test.com”) oMail.Recipients.ResolveAll oMail.SendUsingAccount = oAccount oMail.Send End If End Sub The Account object enables you to test the AccountType property for the type of user email account. The supported types in the OlAccountType enumeration are: ❑ ❑ ❑ ❑ ❑ olExchange olHttp olIMAP ollOtherAccount, used to mark accounts using alternate account providers olPOP3 Although you cannot add new email accounts or configure email accounts using the new accounts properties, the ability to send using a specified account and examine the installed email accounts in an Outlook profile adds features that Outlook developers have long been requesting. The line oMail.SendUsingAccount = oAccount in listing 2.6 does not use the Set keyword to assign the value of the object property SendUsingAccount. When a new instance of an object is created, the Set keyword is used. Set is not used when setting object properties. This syntax is used for all object properties added since Outlook 2003. It was not used before that, which can be confusing when you’re using VBA or VB 6 for Outlook programming. SendUsingAccount does not use Set, but Explorer.CurrrentFolder uses Set even though both are object properties because Explorer .CurrrentFolder was added to the OOM prior to Outlook 2003. 29
Slide 55: Chapter 2: What’s New in Outlook 2007 Performance Improvements Using the Outlook object model to work with large collections of items has always been rather slow. Processing a collection of 15,000 Outlook items and reading a property on each item could literally take hours. However, there are ways of speeding up Outlook object model accesses, such as filtering, restricting a large collection so only items of interest are retrieved, or using the SetColumns method to force Outlook to only load properties of interest. But even with the best optimizations, the code can be slow. Alternatives have included using CDO 1.21 Messages collections instead of Outlook Items collections, but all the alternatives suffered from not being standard or requiring optional components. Working with Tables Instead of Items The new Table objects in Outlook 2007 are a huge performance boost, offering order of magnitude improvements in reading collections of Outlook objects. Tables use rows and columns, where each row is one Outlook item and the columns in the row are the properties that have been retrieved from the item. Tables are read-only, so you have to return the EntryID and StoreID of the item with the row data if you need to retrieve an Outlook item and write to it. Even with this extra step, working with a Table object is still much faster than conventional Outlook methods. Tables are read-only dynamic recordsets that are forward only. Any changes you make to items included in the table are shown in the table. You can only move forward in the table; however, you can reset the table pointer at any time to the start of the table using the Table.MoveToStart method. If there are no rows in the table, or you if have navigated to the end of the table, the Table.EndOfTable Boolean property becomes True, so this property is valuable as a termination condition when looping through a table’s rows. The code in Listing 2.7 retrieves a Table object from the Sent Items folder, removes all the columns in the table, and adds EntryID and Importance columns. It then adds a user property to each item that is marked as High importance. Listing 2.7 Public Sub PlayWithTable() Dim oTable As Outlook.Table Dim colColumns As Outlook.Columns Dim oColumn As Outlook.Column Dim oFolder As Outlook.Folder Dim oMail As Outlook.MailItem Dim oRow As Outlook.Row Dim oProp As Outlook.UserProperty Dim aryValues() As Variant Dim strEntryID As String Set oFolder = _ Application.Session.GetDefaultFolder(olFolderSentMail) Set oTable = oFolder.GetTable ‘returns all items in folder Set colColumns = oTable.Columns colColumns.RemoveAll Set oColumn = colColumns.Add(“EntryID”) Set oColumn = colColumns.Add(“Importance”) 30
Slide 56: Chapter 2: What’s New in Outlook 2007 Listing 2.7 (continued) Do While oTable.EndOfTable = False Set oRow = oTable.GetNextRow aryValues = oRow.GetValues If aryValues(1) = OlImportance.olImportanceHigh Then strEntryID = aryValues(0) Set oMail = _ Application.Session.GetItemFromID(strEntryID) Set oProp = _ oMail.UserProperties.Add(“VeryImportant”, olYesNo) oProp.Value = True oMail.Save End If Loop End Sub Note the following about the code in Listing 2.7: ❑ ❑ You can specify columns you add and remove from a Table object using the Outlook object model name (English only) or any of the appropriate schema names for that column (property). The FolderGetTable method takes two optional arguments. The first argument is a filter, which uses JET or DASL syntax and returns a filtered subset of the items in the folder. The second argument, which defaults to OlTableContents.olUserItems, returns visible items from the Folder. If this argument is set to OlTableContents.olHiddenItems, the table returns the StorageItems in the folder, the collection of hidden folder items. The GetValues method of the Row object returns a 1-dimensional Variant array containing all the requested columns in the table for that row in the table. Users of VB.NET and C# should use Object arrays to retrieve the values from the GetValues method. To retrieve a 2-dimensional array containing all the columns from a specified number of rows, use the Table.GetArray method and specify the number of rows to retrieve with a 32-bit integer value as the argument for GetArray. ❑ ❑ Filtering Tables The code in Listing 2.7 looped through the collection of Rows returned in the Table object and checked each one for a High Importance. You can simplify this code and make it even faster by filtering the Table for only items with High Importance. The code in Listing 2.8 implements a filter for the table based on the Importance level of the items in the folder: Listing 2.8 Public Sub PlayWithFilteredTable() Dim oTable As Outlook.Table Dim colColumns As Outlook.Columns Dim oColumn As Outlook.Column (continued) 31
Slide 57: Chapter 2: What’s New in Outlook 2007 Listing 2.8 (continued) Dim Dim Dim Dim Dim Dim Dim oFolder As Outlook.Folder oMail As Outlook.MailItem oRow As Outlook.Row oProp As Outlook.UserProperty aryValues() As Variant strEntryID As String strFilter As String Set oFolder = _ Application.Session.GetDefaultFolder(olFolderSentMail) strFilter = “[Importance] = 2” Set oTable = oFolder.GetTable(strFilter) Set colColumns = oTable.Columns colColumns.RemoveAll Set oColumn = colColumns.Add(“EntryID”) Set oColumn = colColumns.Add(“Importance”) Do While oTable.EndOfTable = False Set oRow = oTable.GetNextRow aryValues = oRow.GetValues If aryValues(1) = OlImportance.olImportanceHigh Then strEntryID = aryValues(0) Set oMail = _ Application.Session.GetItemFromID(strEntryID) Set oProp = _ oMail.UserProperties.Add(“VeryImportant”, olYesNo) oProp.Value = True oMail.Save End If Loop End Sub Please note the following about the code in Listing 2.8: ❑ The filter you use can be a standard Outlook filter string or a DASL syntax filter string. If you use a DASL filter, you must precede it with @SQL=. For example, you could have expressed the filter in the previous example as @SQL=http://schemas.microsoft.com/mapi/proptag/ 0x00170003 = 2. Another type of DASL filter uses the new Content Indexer in Outlook, Filters of that type are discussed in the section “The Importance of DASL.” The Table object allows you to retrieve a filtered table, or to retrieve an unfiltered table and filter the table on the fly using the FindRow(strfilter) and FindNextRow methods. You can also restrict the table, using the Table.Restrict method. You can also sort a table in either ascending or descending order. ❑ Filtering and restricting a table have performance and store load implications. Filtering a table is faster if there are a small number of items in the table. If only a small subset of items in a large table is required, restricting is faster than filtering. These performance issues are the same as those you face when you decide to use a filter or a restriction on an Outlook Items collection. 32
Slide 58: Chapter 2: What’s New in Outlook 2007 Table Default Columns By default, tables return certain default columns. All tables return, in this order: 1. 2. 3. 4. 5. EntryID Subject CreationTime LastModificationTime MessageClass Each type of item also returns additional default columns, as shown in the following table. Item Type Calendar Item Column # 6. 7. 8. Contact Item 6 7. 8. Task Item 6. 7. 8. Default Columns Start End IsRecurring FirstName LastName CompanyName DueDate PercentComplete IsRecurring If you need to retrieve other properties, you can add additional columns or you can remove all existing columns and just add the ones you need. Table Limitations Tables have certain limitations, depending on the store provider and property types. There are four limitations of which you need to be aware when using tables: ❑ Depending on the store provider, you may only be able to retrieve 255 characters from string properties. Exchange is an example of a store provider that can only return 255 characters in a table. You can either check to see if the store is an Exchange store or use the NameSpace .GetItemFromID method to retrieve the Outlook item and read the property value from the item. 33
Slide 59: Chapter 2: What’s New in Outlook 2007 ❑ If a Date value property is defined as part of the Outlook object model and is retrieved using the Outlook property name, for example, CreationTime, the date value is returned in local time. If the column is retrieved using a schema name the value is returned in UTC, and you must use the Row.UTCToLocalTime method to convert the value to local time. To convert a local time retrieved from a row, use the Row.LocalTimeToUTC method. These methods round any times to the nearest minute. Properties that are defined in Outlook as strings but stored internally as binary properties are returned as strings if accessed using the Outlook property name, for example EntryID or StoreID. If the property is not defined in the Outlook object model or is retrieved using a schema name, the column is returned as a binary array. You can use the Row.BinaryToString method to convert the binary array into a string value. You cannot add computed property values such as Sent or AutoResolvedWinner to the Columns collection. ❑ ❑ Security Since its introduction in Outlook 2000 SP2, the Outlook object model guard has been a source of frustration for Outlook developers. It displays various security dialogs when any restricted property or method is accessed using the Outlook object model or CDO 1.21. The object model guard restricts such accesses as reading the Body property of an item because it can be used to harvest email addresses. Many other properties and methods are also restricted in the object model guard, the complete list is at MSDN, in an article “Code Security Changes in Outlook 2007,” which also lists changes in how the object model guard is applied. This article is located at http://msdn2.microsoft.com/en-us/library/ms778202.aspx. This section lists the changes that make it far easier to live with the Outlook object model guard than ever before in Outlook 2007. The Web page www.outlookcode.com/d/sec.htm contains additional information on the object model guard for developers working with untrusted code and code that must run on earlier versions of Outlook. Outlook code falls into one of two categories: ❑ ❑ Trusted code has access to all the properties and methods in the object model, and never causes a security prompt from the object model guard. Untrusted code causes a security prompt or failure of a method or property access with an error when accessing a restricted property or method. In previous secure versions of Outlook, all out-of-process code running in other applications was always untrusted. In-process code running in Com addins was untrusted in Outlook 2002 and earlier, as was VBA macro code. In Outlook 2003, COM addins and VBA code were trusted if they derived all Outlook objects from the Application object passed in the COM addin’s startup code or from the intrinsic Application object in VBA code. Untrusted code or code that had to run on multiple versions of Outlook had to use Extended MAPI code or third-party libraries to avoid the Outlook object model guard. However, Extended MAPI is very difficult to learn and work with and can only be programmed using C++ or Delphi. Extended MAPI is not supported in any .NET language. The use of third-party libraries is the standard for most Outlook code that must run across multiple Outlook versions, but some organizations don’t want to use third-party libraries or require the source code for any third-party libraries used. 34
Slide 60: Chapter 2: What’s New in Outlook 2007 Trusted Code By default, all Outlook 2007 code, both in process and out of process, is trusted if the user is running up-to-date antivirus software that is indicated as in good health status in the Windows Security Center. This is a huge change in security, because now code running in a Word or Excel macro, or an .exe or .dll program can be treated as trusted code. If group policies have been implemented for programmability, or the Exchange public folder security form is in use, the settings applied in the policy or form are honored. However, under normal circumstances, most Outlook code can now run as trusted code on secure computers. If “healthy” antivirus software isn’t running on an Outlook 2007, computer code is trusted if it’s running in process with Outlook. That means if you derive your Outlook objects from the available intrinsic Application object, your COM addin and VBA code is trusted. You can check to see if your application is trusted by checking the new Application.IsTrusted Boolean property. Untrusted Code Untrusted code will cause the security prompts to appear when the code accesses any restricted property or method. The new PropertyAccessor and Table objects are not available in untrusted code and will return errors if used in untrusted code. In general, restricted properties and methods can be used to harvest address or private information, return AddressEntry objects, save to external storage, access properties with schema names, and send Outlook items. Your only alternative if you need to access any restricted properties or methods is to use Extended MAPI or a third-party library, such as Redemption or MAPI33. You can learn more about these alternate libraries in Appendix B. The Impor tance of Using DASL This section uses a number of terms that may not be familiar to all Outlook developers such as DAV, DASL, JET, and CI. Some DASL syntax was used earlier in this chapter to set up filters and property schemas for the PropertyAccessor and Table objects, so at least the look of DASL should be becoming somewhat familiar. DAV is an abbreviation for WEBDav, which stands for Web Distributed Authoring and Versioning. This is an Internet standard, which is discussed at www.ietf.org/html.charters/webdav-charter.html, if you are interested in learning more about this term. For this book, the use of DAV is mostly about the search features implemented in DASL. DASL is an abbreviation for DAV Searching and Locating query syntax. Outlook has its own implementation of DASL, which is what is used in this book. DASL is used for creating queries for search folders, restrictions for Table and Items collections, and the schemas used in DASL form the basis of the property schemas used for the PropertyAccessor. 35
Slide 61: Chapter 2: What’s New in Outlook 2007 JET query syntax is based on the Access Expression Service. JET searches have been the standard used for most Restriction and Filter clauses for Items collections, such as “[Subject] = ‘Test’“. This syntax is still supported in Outlook 2007, but DASL and CI searches can be more flexible and in the case of CI searches far faster than using JET syntax. CI search syntax uses Content Indexer instant search syntax that queries the indexes created by the new Outlook full-text indexing engine. This instant search engine may not be installed or running, so before using any CI searches always test for Store.IsInstantSearchEnabled, a Boolean property. DASL Namespaces Outlook supports various DASL schemas and namespaces, as you’ve learned in this chapter. These namespaces are oriented to specific MAPI, Office, or DAV schemas. The following namespaces are specifically supported in Outlook: “DAV:” “urn:schemas:calendar:” “http://schemas .microsoft.com/mapi/ proptag/0x” “http://schemas.microsoft .com/mapi/id/“ “urn:schemas:httpmail:” “urn: schemas-microsoft-com: office:office#“ “urn: schemas-microsoft-com: office:outlook#“ “http://schemas.microsoft .com/exchange/“ “urn:schemas: mailheader:” “http://schemas.microsoft .com/mapi/string/“ “urn:schemas:contacts:” You can find more information about these various namespaces at the following Web URLs: ❑ ❑ ❑ MAPI format and namespace: http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/e2k3/e2k3/_cdo_schema_mapi.asp Office format and namespace: http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/e2k3/e2k3/_cdo_schema_office.asp DAV formats and namespaces such as: http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/e2k3/e2k3/_cdo_schema_calendar.asp Using DASL and JET Syntax Searching in Outlook can be confusing because two types of search syntax can be used. The older and more familiar JET syntax references properties by using their names, which is more readable, but only searches on properties exposed in the Outlook object model. The newer DASL syntax is more flexible and allows more comparison operators but is less readable. DASL references properties using property schemas and can search on properties not exposed in the Outlook object model. Understanding both types of searching is important to get maximum performance in your Outlook applications. 36
Slide 62: Chapter 2: What’s New in Outlook 2007 JET Syntax JET syntax uses only standard property names, such as “[Subject]“ or “[Location]“. You can also reference user defined properties in JET syntax, but only if the specific UserProperty is exposed as a property in the folder. User properties added only to specific items cannot be used in a JET syntax search or query. Note the following about JET syntax: ❑ ❑ JET allows a limited set of comparison operators: =, <>, <, >, >=, and <=. You cannot use substring searches or qualifiers such as “begins with” or “includes”. JET syntax is supported for Items.Find and Items.Restrict, for Table.FindRow and Table.Restrict, and for Folder.GetTable. JET syntax is not supported for View filters or the AdvancedSearch method. You will find many examples of JET search syntax in this book, but it isn’t discussed further in this chapter, because it’s not a new technology for Outlook. DASL Syntax DASL syntax is a newer technology for Outlook and in addition to the comparison operators used by JET, DASL syntax also can use other SQL query type operators such as LIKE and IS. Although DASL search syntax is newly supported for Outlook 2007 in areas such as Items.Find and Items.Restrict, you can also use the syntax with Outlook 2002 and 2003. Microsoft doesn’t document or support that, but a DASL search string preceded by “@SQL=” will work for filters and restrictions in Outlook 2002 and 2003. DASL syntax is supported for Items.Find and Items.Restrict, for Table.FindRow and Table.Restrict, and for Folder.GetTable. For all those areas, the search string must be preceded by @SQL=, with no spaces between that string and the DASL search string. For use in AdvancedSearch and view filters, you must not use the @SQL= string at the beginning of the search string. Some examples of using DASL search syntax for an Items.Restrict clause are: ❑ ❑ ❑ ❑ ❑ @SQL=urn:schemas:httpmail:subject LIKE ‘%store%‘: Searches for items with a Subject including the substring store. @SQL=urn:schemas:httpmail:importance = 2: Searches for items with a High Importance. @SQL=http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000000000000046}/81050040 <= ‘today’: Searches for tasks that are overdue or due today. @SQL=urn:schemas:httpmail:subject IS NULL: Searches for items with a blank Subject. @SQL=NOT(urn:schemas:httpmail:subject IS NULL): Searches for items with a Subject that isn’t blank. CI Syntax The new CI syntax is a subset of DASL syntax, but you can expect it to be much faster for searches in large datasets. Of course, that’s only if the instant search feature is installed, which is optional. Even if instant search is installed, it may be disabled, so always check the IsInstantSearchEnabled Boolean property of the Store object before attempting to use a CI syntax search for any particular store. Because 37
Slide 63: Chapter 2: What’s New in Outlook 2007 this property is set on a store-by-store basis, make sure to check it for any store you are searching. Note the following about CI syntax: ❑ CI searches provide two operators you can use, CI_STARTSWITH and CI_PHRASEMATCH. One important thing to remember is that mixing JET and DASL syntax is not permitted in a search and will cause an error. CI search keywords are only permitted in an Items.Restrict or Table.Restrict filter clause. They are not permitted for Table.FindRow or Items.Find filter clauses. CI_STARTSWITH searches for a match on the beginning of a word, using a case insensitive search. For properties that can contain more than one word the match can be on any word in the property. For properties such as Subject, the initial RE: or FWD: (if any) is ignored. CI_PHRASEMATCH searches for a word or words in a property that exactly match the search ❑ ❑ ❑ phrase. This search is also case-insensitive, and the word or words do not have to be the first word or words in the property. ❑ Operators such as LIKE or = can only be used in non-CI searches that use DASL syntax. Here are some examples of CI searches: ❑ urn:schemas:httpmail:subject CI_STARTSWITH ‘stor’: Searches for items with a Subject including words that start with “stor”. This search returns “store” and “storage” but does not return “restore”. ❑ urn:schemas:httpmail:subject CI_PHRASEMATCH ‘install’: Searches for items with a Subject including words or phrases that start with “install”. This search does not return “installation”. ❑ urn:schemas:httpmail:subject CI_PHRASEMATCH ‘install from disk’: Searches for items with a Subject including words or phrases that start with “install from disk”. This search returns “Install from disk” and “install from disk drive” but not “installed from disk”. CI searches may seem limited due to only offering two keywords, but their speed makes them the first choice if you are working with an object that supports CI searching and your search can conform to using only the CI_STARTSWITH or CI_PHRASEMATCH keywords. Summar y In this chapter, you learned about some of the new features in the Outlook object model. Important new bjects such as the PropertyAccessor and Table objects were introduced and discussed in some depth, while other new features such as stores, accounts, and interface objects were briefly introduced. A discussion about Outlook security introduced the new, more relaxed security restrictions for Outlook 2007 code development. New performance enhancements were discussed in relation to the Table object, and various search and schema syntaxes such as DASL, JET, and CI were introduced. With this foundation, you are now ready to start exploring the many new ways to work with Outlook code. In the next chapter, you will start exploring in depth the top-level Outlook development objects such as NameSpace and the Explorers and Inspectors collections, as well as some of the new forms and user interface elements in Outlook 2007. 38
Slide 64: Outlook Development This chapter focuses on the Application and NameSpace objects. As you’ve already learned in Chapter 1, these objects are your entry point into Outlook programming and are used throughout your Outlook applications. To help you understand the Application object, this chapter introduces methods for instantiating globally available and trusted Application and NameSpace objects for COM addins programmed using languages such as VB.NET and C#, with Visual Studio 2005 and VSTO 2005 SE development platforms. You then learn about some of the new methods, properties, and events of the Application object. You discover how to program context menus, and you get to work with context menus in many of the examples in this book. To better understand the NameSpace object, this chapter introduces you to the new Categories collection and Category objects, Exchange information, and user interface dialogs. The version of VSTO used for Outlook 2007 development is VSTO 2005 SE. It is referred to as VSTO in this book. The Application Object The Outlook object model guard is much less intrusive in Outlook 2007 than in any previous secure version of Outlook, as you learned in Chapter 2. There are times, however, when tighter security is in effect, so it’s important to handle the Application object correctly, and to verify if your code is trusted. Even if your code is running on Outlook 2003, security policies are more relaxed for trusted code so it’s important to handle Application correctly. Form and VBA code have intrinsic Application objects, and COM addins are passed an Application object in their OnConnection events. This Application object is what’s trusted, and all Outlook objects must be derived from this object, or they won’t inherit this trust. Code can verify trusted status by checking the new IsTrusted property. Generally, the Application object is stored in a global variable or as a public property of a class that has scope during the life of the application.
Slide 65: Chapter 3: Outlook Development Outlook form code also has an intrinsic Item object that refers to the item in which the code is running. The Item object in form code is also a trusted object, much like Application. You will learn about form coding in Chapter 5. The code in Listing 3.1 shows how to initialize globally trusted Outlook Application and NameSpace objects that you can use throughout an application. Both objects are exposed as public properties of a class that is available throughout the application. The class is only initialized if the Application object is trusted. Code for a COM addin is shown in both VB.NET and C# using Visual Studio 2005. Only VSTO startup and shutdown code is shown; the class code is unchanged when used with VSTO. Listing 3.1: VB.NET Private applicationObject As Outlook.Application Public gClass As Class1 Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection applicationObject = TryCast(application, _ Outlook.Application) If applicationObject.IsTrusted Then gClass = New Class1(applicationObject) End If End Sub Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnDisconnection ‘ call to release all other objects Call Shutdown applicationObject = Nothing gClass = Nothing End Sub ‘ Beginning of the Class1 definition Imports Outlook = Microsoft.Office.Interop.Outlook Friend Class Class1 ‘ The WithEvents declarations allow handling ‘ events for the Application and NameSpace objects Private WithEvents _app As Outlook.Application Private WithEvents _ns as Outlook.NameSpace Public Sub New(ByVal app As Outlook.Application) _app = app _ns = _app.GetNameSpace(“MAPI”) End Sub 40
Slide 66: Chapter 3: Outlook Development Listing 3.1: VB.NET (continued) Public ReadOnly Property Application() As Outlook.Application Get Return _app End Get End Property Public ReadOnly Property NameSpace() As Outlook.NameSpace Get Return _ns End Get End Property End Class VB.NET with VSTO public class ThisAddIn Friend gClass As Class1 Private Sub ThisApplication_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup If Me.IsTrusted Then gClass = New Class1(Me.Application) End If End Sub Private Sub ThisApplication_Shutdown(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Shutdown ‘ call to release all other objects Call Shutdown gClass = Nothing End Sub End class C# namespace CS_Addin { using Outlook = Microsoft.Office.Interop.Outlook; public partial class Connect : Object, Extensibility.IDTExtensibility2 { private Outlook.Application m_Application; public Class1 gClass; public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom) { (continued) 41
Slide 67: Chapter 3: Outlook Development C# (continued) m_Application = application as Outlook.Application; if(m_Application.IsTrusted) { gClass = new Class1(m_Application); } } public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom) { // call to release all other objects ShutdownAddin(); m_Application = null; gClass = null; } // Other code before the closing brace of the Connect class and namespace } } // Beginning of the Class1 class using Outlook = Microsoft.Office.Interop.Outlook; namespace CS_Addin { class Class1 { private Outlook.Application _app; private Outlook.NameSpace _ns; // Class Constructor public Class1(Outlook.Application app) { _app = app; _ns = _app.GetNamespace(“MAPI”); } public Outlook.Application Application { get { return _app as Outlook.Application; } } public Outlook.NameSpace NameSpace { get { return _ns as Outlook.NameSpace; } } } } 42
Slide 68: Chapter 3: Outlook Development C# with VSTO namespace CS_Addin { public partial class ThisAddIn { private Outlook.Application m_Application; public Class1 gClass private void ThisApplication_Startup(object sender, System.EventArgs e) { m_Application = this.Application as Outlook.Application; if(m_Application.IsTrusted) { gClass = new Class1(m_Application); } } private void ThisApplication_Shutdown(object sender, System.EventArgs e) { // call to release all other objects ShutdownAddin(); m_Application = null; gClass = null; } It’s very important that you release all of your Outlook objects in the appropriate shutdown procedure. Outlook 2007 takes extra measures to ensure that Outlook can close even if global objects (and event handlers if necessary) aren’t released, but it doesn’t always succeed. Earlier versions of Outlook will hang in memory if your global objects aren’t released. The code releases global Outlook and class objects and calls a ShutdownAddin procedure that isn’t shown to release any other global objects. In general, Outlook 2007 is less sensitive to shutdown problems than earlier versions of Outlook. VSTO addins are less sensitive to shutdown problems than addins created using only Visual Studio 2005. If you are programming Outlook on the .NET platform, you might have to take additional steps to ensure that Outlook can close properly, especially if you are supporting earlier versions of Outlook. Explicitly calling the garbage collector and waiting for it to complete may be necessary. If you program in C#, you have to unsubscribe from any event handlers to which you are subscribed. VSTO Outlook addins use a special Startup method in their ThisAddIn class in place of the OnConnection event. The details of handling the IDTExtensibility2 interface that provides the connection between the addin and Outlook are handled behind the scenes by VSTO. The objects and properties provided in OnConnection are available in the System.EventArgs variable in the Startup method. ❑ VB.NET: OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection VB.NET with VSTO: Private Sub ThisAddIn_Startup(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Startup ❑ 43
Slide 69: Chapter 3: Outlook Development The call to initialize the instance of Class1 is made using the Me.Application object in VSTO VB.NET addins, which represents the Application object. VSTO C# addins use the this.Application object instead of Me.Application. When using the Application object in Outlook VBA or forms code, the object is already global, so you don’t have to instantiate a global object. A global NameSpace object would still have to be instantiated. If you’ve used VSTO 2005 with Outlook 2003, there are some differences in VSTO 2005 SE, the VSTO version used for Outlook 2007 development. VSTO 2005 SE uses ThisAddIn instead of ThisApplication and exposes the Outlook.Application object differently. The following table shows how to reference the Application object in both versions of VSTO. VSTO 2005 SE VB C# Me.Application this.Application VSTO 2005 Me this New Methods, Properties, and Events The following new methods, properties, and events have been added to the Application object for Outlook 2007. GetObjectReference GetObjectReference(Item As Object, ReferenceType) is a method you use to get either a strong or weak reference to an object. A strong reference is a reference that won’t get garbage collected until the reference is explicitly released. A weak reference is a reference that can be released by the garbage collector at any time, so the reference essentially must be used immediately while you know it’s still valid. Weak references are mostly for objects that have a large memory impact that you can re-reference, if needed, but that you don’t want to persist if you don’t need to. The call to GetObjectReference(Item As Object, ReferenceType) returns an Object reference and is passed either OlReferenceType.olStrong or OlReferenceType.olWeak as the ReferenceType argument. DefaultProfileName DefaultProfileName is a read-only property that returns a string specifying the default profile name of the Outlook setup. The default profile name is not necessarily the current profile name in the current Outlook session. The currently active Outlook profile can be retrieved using the NameSpace.CurrentProfileName property. IsTrusted IsTrusted is a new, Boolean read-only property that lets you know if your code is running under a trusted context. Outlook 2007 trusts more code than previous secure versions of Outlook, including in many cases out of process code such as code running in .exe applications and as macros in other Office applications such as Excel, but there are times when your code won’t be running in a trusted context and 44
Slide 70: Chapter 3: Outlook Development certain parts of the Outlook object model won’t be available to you, such as the PropertyAccessor object. In those cases, you must use workarounds or handle the untrusted condition in other ways. Context Menus The new events presented in the following table are fired when a context menu is about to display in Outlook, the result of right-clicking in an area of the Outlook UI. Outlook developers have requested this feature for years. Before Outlook 2007, you could only use a hack of trapping the Office.CommandBars .OnUpdate event, which did not return the context that was clicked and was prone to various problems such as crashing Outlook. An example of using the OnUpdate event is shown at www.outlookcode.com/codedetail .aspx?id=314, along with comments that indicate the problems to which this method is prone. The CommandBar argument, passed to all the event handlers shown in the following table, is used as the target for any new menu items you are adding, removing, or repurposing in the context menu that is about to be displayed. These events all fire before the context menu is displayed. Event AttachmentContextMenuDisplay(CommandBar As CommandBar, Attachments As AttachmentSelection) Purpose Fires when you right-click the attachment well that shows the attachments in an Outlook item, either in a folder view in the reading pane (Explorer) or in an open Outlook item (Inspector). The AttachmentSelection collection passed as an input to this event handler contains all the attachments selected in the attachment well, allowing you to process all selected attachments. Fires when you right-click a folder in the Navigation Pane, passing the Folder object that was clicked. This event fires when you rightclick a folder anywhere in the Navigation Pane, including in the lists of folders such as My Calendars. Fires when you right-click one item or a selection of items in a folder view. Fires when you right-click an item listed in the Shortcuts section of the Navigation Pane. The new OutlookBarShortcut object has a Target property that returns a Folder object for selected folder shortcuts, including shortcuts to the new To-Do List folder, or a string URL for shortcuts to Web URLs such as Microsoft Office Online. Continued FolderContextMenuDisplay(CommandBar As CommandBar, Folder As Folder) ItemContextMenuDisplay(CommandBar As CommandBar, Selection As Selection) ShortcutContextMenuDisplay(CommandBar As CommandBar, Shortcut As OutlookBarShortcut) 45
Slide 71: Chapter 3: Outlook Development Event StoreContextMenuDisplay(CommandBar As CommandBar, Store As Store) Purpose Fires when you right-click a Store object in the Navigation Pane, such as Personal Folders, Mailbox - Casey Slovak or Exchange Public Folders. Fires when you right-click a view in a folder view or in the To-Do Bar Fires when a context menu is about to close. The OlContextMenu enumeration contains members for each type of context menu for which there is a display event, such as Item or Store. This event is used to clean up any changes you’ve made to the context menu or anything you’ve instantiated during the use of your display event handler. ViewContextMenuDisplay(CommandBar As CommandBar, View As View) ContextMenuClose(ContextMenu As OlContextMenu) You can place the following code in the ThisOutlookSession class module in the Outlook VBA project. It demonstrates adding a menu control to the context menu that’s displayed when you right-click an item in an Outlook folder, in this case the Inbox or other email folder. The menu item is added as a temporary CommandBarButton control that fires the oButton_Click event when the menu item is clicked. The menu item displays the subject of the selected email item and the click event displays a message box. The cleanup for this menu item is performed in the ContextMenuClose event handler, where the menu item is deleted and the referenced CommandBar and CommandBarButton objects are set to Nothing (null in C#). These object references are declared as module level class objects, so they retain scope after the procedure instantiating them is finished. Private WithEvents oButton As Office.CommandBarButton Private oBar As Office.CommandBar Private Sub Application_ContextMenuClose(ByVal ContextMenu _ As OlContextMenu) Dim oControl As Office.CommandBarControl If ContextMenu = OlContextMenu.olItemContextMenu Then Set oControl = oBar.FindControl(msoControlButton, , “MyTag”) If Not (oControl Is Nothing) Then oControl.Delete Set oButton = Nothing Set oBar = Nothing End If End If End Sub Private Sub Application_ItemContextMenuDisplay(ByVal CommandBar _ As Office.CommandBar, ByVal Selection As Selection) Dim strCaption As String Dim strTag As String 46
Slide 72: Chapter 3: Outlook Development If (Selection.Count = 1) And (Selection.Item(1).Class = _ OlObjectClass.olMail) Then strCaption = Selection.Item(1).Subject & “ Context Menu” strTag = “MyTag” Set oButton = _ CommandBar.Controls.Add(msoControlButton, , , , True) oButton.Caption = strCaption oButton.Tag = strTag Set oBar = CommandBar End If End Sub Private Sub oButton_Click(ByVal Ctrl As Office.CommandBarButton, _ CancelDefault As Boolean) MsgBox “My button clicked” End Sub The module level CommandBar object is declared so that it’s easy to work with the context menu CommandBar; otherwise, it would be very hard to locate it among the overall CommandBars collection. A unique Tag property is assigned to the button when it’s created so that the button can be found and deleted when the context menu is closed. Unique Tag properties are also important when handing multiple Inspectors and Explorers, so each button you create in an Explorer or Inspector can later be located and so the button’s Click event only fires in the desired event handler and not in event handlers in every open Inspector or Explorer. Various variations of Visual Basic allow passing a missing parameter in a function call as a missing argument with just the comma as a placeholder. If you are using C# you have to supply each specified argument in the parameter list, you cannot just use comma placeholders. This can be done by declaring a reference to private object System.Reflection.Missing.Value and using that reference wherever there’s a missing argument. C# use of missing parameter values: private object missing = System.Reflection.Missing.Value; oButton = CommandBar.Controls.Add(MsoControlType.msoControlButton, missing, missing, missing, true); BeforeFolderSharingDialog The BeforeFolderSharingDialog(FolderToShare As Folder, Cancel As Boolean) event fires just before the dialog for sharing a folder is displayed. For example, when using Exchange server, you can select to share your calendar folder with someone. If you right-click the Calendar folder in the Navigation Pane and select Share Calendar, the sharing dialog is displayed and the event fires. This applies to any folder that can be shared. If you set the Cancel property equal to True in your event handler code, the dialog display is 47
Slide 73: Chapter 3: Outlook Development canceled. This provides a way of repurposing the existing menu item to perform your own function either in addition to or instead of the original function. You can also perform pre-processing work that you might want, for example, setting up some properties in the shared folder before the dialog is displayed. If you create a sharing item using the NameSpace.CreateSharingItem method to create a sharing item the BeforeFolderSharingDialog event handler will not fire. ItemLoad The ItemLoad event provides an Object representing the item about to be loaded into memory. Outlook loads an item into memory when the item is accessed in any way, for example when you view it in the Reading Pane or right-click an item and select it to add a follow-up flag. Previous versions of Outlook had no way to detect those actions using the Outlook object model. Prior to Outlook 2007, Extended MAPI code was the only way to detect when Outlook loaded an item into memory. The Object is a weak reference that only provides Class and MessageClass as valid properties. All other item properties are unavailable and return an error if referenced in this event. As a weak reference, you cannot control when Outlook unloads this reference, so if you want to preserve this reference you must assign it to another, strong reference. When the reference is about to be destroyed, the Unload event fires, as shown in the following code. Dim WithEvents oMail As Outlook.MailItem Private Sub Application_ItemLoad(ByVal Item As Object) If Item.Class = olMail Then Set oMail = Item End If End Sub Private Sub oMail_Unload() ‘ Use this event to unload any wrapper class ‘ instantiated to work with loaded items. End Sub One way of using the ItemLoad event is to detect changes made to items during in-cell editing. A wrapper class that handles the Unload event as well as any other item events in which you’re interested is created to handle each item that’s loaded. These wrapper classes are stored or referenced in a global collection or a hashtable to prevent them from going out of scope or being garbage collected. For changes made during in-cell editing, events for PropertyChange and UserPropertyChange are handled. There is no way to determine when Outlook will unload items from memory. Other Important Collections, Methods, Properties, and Events The Explorers collection and Explorer object are one of the ways you interact with the Outlook user interface, with an Explorer object used to display an Outlook folder. The Explorers collection contains all currently open Explorer objects, with ActiveExplorer the currently active Explorer object. 48
Slide 74: Chapter 3: Outlook Development The following code retrieves the ActiveExplorer as well as the currently displayed folder in the Explorer. If the folder is a mail folder, the code turns on the To-Do Bar. Dim oExpl As Outlook.Explorer Dim oFolder As Outlook.Folder If Not (Application.ActiveExplorer Is Nothing) Then Set oExpl = Application.ActiveExplorer Set oFolder = oExpl.CurrentFolder If oFolder.DefaultItemType = OlItemType.olMailItem Then Call oExpl.ShowPane(OlPane.olToDoBar, True) End If End If Inspectors are used to display Outlook items, and the ActiveInspector object is the currently active displayed item. If no Outlook items are displayed, ActiveInspector is Nothing. The item being displayed in the Inspector is retrieved using the CurrentItem property, as the following code illustrates. Dim oInsp As Outlook.Inspector Dim oMail As Outlook.MailItem If Not (Application.ActiveInspector Is Nothing) Then Set oInsp = Application.ActiveInspector If oInsp.CurrentItem.Class = OlObjectClass.olMail Then Set oMail = oInsp.CurrentItem End If End If ActiveWindow returns an Object that represents the currently active Outlook window, either an Explorer or Inspector. If Outlook isn’t displaying a UI, ActiveWindow returns Nothing. You use this procedure to work with the active Outlook window, as well as to find out where the user is working at any point in time. The following code checks the active window type and instantiates an Outlook object based on the window type. Dim oInsp As Outlook.Inspector Dim oExpl As Outlook.Inspector Dim oWindow as Object Set oWindow = Application.ActiveWindow If Not (oWindow Is Nothing) Then If TypeName(oWindow) = “Inspector” Then Set oInsp = Application.ActiveInspector ElseIf strType = “Explorer” Then Set oExpl = Application.ActiveExplorer End If End If 49
Slide 75: Chapter 3: Outlook Development CreateItem and CreateItemFromTemplate CreateItem and CreateItemFromTemplate are some of the ways to create new Outlook items. CreateItemFromTemplate creates new items from published Outlook forms that are saved as .OFT files. CreateItem takes a member of the OlItemType enumeration as its argument and creates a new item of that type, as shown in the following code: Set oContact = Application.CreateItem(OlItemType.olContactItem) Set oAppointment = Application.CreateItemFromTemplate(“C:\Forms\Appt.oft”, _ oMyCalendar) CreateItem creates the new item in the default folder for that item type. For mail items, the default folder is Drafts. To create an item in a folder other than the default folder, use the Add method of the folder’s Items collection. Another option is to use CreateItem and move the item to the correct destination folder after the item is created. You use the optional InFolder Folder argument for CreateItemFromTemplate to create the item in the specified folder. NewMail and NewMailEx NewMail is one of the most useless events in the Outlook object model. It fires only at intervals of every few minutes and misses any incoming item that arrives too soon after the previous NewMail event has fired. It also doesn’t tell you which item just arrived in the Inbox. NewMailEx is a much better event to use to monitor incoming items. It passes a string that contains a list of the EntryID properties, separated by commas, of the items that have arrived since the last NewMailEx event. You use the text processing to collect an array of EntryID properties. The items are retrieved using the NameSpace.GetItemFromID(EntryID) method. The NewMail event has a major limitation, in that if more than 16 items arrive at once the event won’t fire at all. The underlying MAPI, which only fires a change event if more than 16 items arrive at one time, causes this limitation. This limitation is shared with the events that monitor Add, Change and Remove for a folder’s Items collection. A limitation of the NewMailEx event is that when over 1000 items are received at once, NewMailEx misses some of the items. This limitation can usually be ignored because it happens so infrequently. Reminder and Reminders The Reminder(Item As Object) event fires when a reminder is about to be displayed. The Item object can be any Outlook item that supports a reminder. Reminders are supported for Mail, Contact, Appointment, Meeting, Task, and Post items. Journal and Note items don’t support reminders. The Reminders collection is the complete collection of items in the Reminders search folder. This collection provides events for when reminders are added, removed and changed. Other events are provided for when a reminder snoozes or fires and before the Reminders window is displayed. These events are useful when you process reminders, for example, to cancel showing the Reminders window and sending an email instead when a reminder fires. The Reminders collection also provides access to individual Reminder items. 50
Slide 76: Chapter 3: Outlook Development The following code sample initializes a Reminders collection object and shows an event handler for sending an email when a reminder fires. The Reminders window is canceled and the reminder snoozes for 1 hour. The initialization code can be in any class or code module in the VBA project. The event handler must be in a class module or UserForm. For this code, the intrinsic ThisOutlookSession can be used. ‘Initialization code in ThisOutlookSession Private WithEvents colReminders As Outlook.Reminders Private Sub Application_Startup() Set colReminders = Application.Reminders End Sub ‘Event handlers Private Sub colReminders_BeforeReminderShow(Cancel As Boolean) Cancel = True ‘cancel showing reminders window End Sub Private Sub colReminders_ReminderFire(ByVal ReminderObject _ As Reminder) Dim oMail As Outlook.MailItem Set oMail = Application.CreateItem(olMailItem) With oMail .Subject = ReminderObject.Caption .Body = “Reminder: “ & CStr(ReminderObject.NextReminderDate) .To = “tester@test.org” ‘substitute real email address .Recipients.ResolveAll .Send End With ReminderObject.Snooze 60 ‘60 minutes End Sub Startup, MAPILogonComplete, and Quit The Startup and MAPILogonComplete events fire in that order. The Startup event handler is used to initialize any application-wide events in which you are interested and initialize any global variables. MAPILogonComplete fires after Outlook finishes logging in to the MAPI store provider, usually a PST file or an Exchange mailbox. COM addins mostly use the Startup or OnConnection events instead of Application_Startup for initializations. Application_Startup is used mostly in Outlook VBA code and out-of-process applications that have started Outlook. By the time the Quit event fires in Outlook VBA, all Outlook objects are already out of scope and will cause an exception if referenced. ItemSend The ItemSend(Item As Object, Cancel As Boolean) event is an application-wide event that fires every time any Outlook item is sent out through the email transport. The item being sent is passed to the event-handling procedure as a general Object because the item may also be a task request, a meeting request, or other type of item. To check what type of item is passed to the event handler, use Item.Class and test for values representing members of the OlObjectClass enumeration. 51
Slide 77: Chapter 3: Outlook Development You can use ItemSend for purposes such as attaching corporate disclaimers to emails and adding required recipients for meetings, as the following code shows. VB.NET Private Const Disclaimer = “This message is private” Private Sub Application_ItemSend(ByVal Item As Object, _ Cancel As Boolean) If Item.Class = olMail Then ‘ If a MailItem ‘ If this is HTML use HTMLBody for ‘ formatting the text. Item.Body = Item.Body & vbCRLF & Disclaimer End If End Sub C# // Hook up the event handler Application.ItemSend += new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ ItemSendEventHandler(Application_ItemSend); // This can be any disclaimer message const String Disclaimer = “This message is private”; private void Application_ItemSend(object Item, ref bool Cancel) { try { // Try casting the Item to an Outlook.MailItem Outlook.MailItem oMail = (Outlook.MailItem)Item; // Add a newline and the disclaimer. // If this is HTML use HTMLBody for // formatting the text. Item.Body = Item.Body + “\n” + Disclaimer; } catch { // Any error in the type casting comes here } } Version The Version property returns a string that tells you what version of Outlook is running. This is important if you plan to support multiple versions of Outlook, if you only want your code to run on certain Outlook versions, or if you support certain features only with specific versions of Outlook. The following table lists the leftmost part of the Version property for each version of Outlook. The Version property string for Outlook 2007 would be something like “12.0.0.4518”, identifying the leftmost two digits of the string as “12” would identify the running Outlook version as 2007. 52
Slide 78: Chapter 3: Outlook Development Outlook Version 97 98 2000 2002 2003 2007 Value “8.0” “8.5” “9.0” “10.0” “11.0” “12.0” The NameSpace Object The NameSpace object is the current Outlook session. Only one Outlook session can run at a time, and to switch sessions you must exit and restart Outlook. The Session, an alias for NameSpace, logs in to a specific Outlook profile, which provides the settings for which data store to use and which email accounts are configured, among other things. The NameSpace object in Outlook 2007 provides new methods and properties you learn about in this section. CompareEntryIDs is a new method that determines if two objects are the same based on a comparison of their EntryID properties. You can use this method not only for Outlook items, such as mail items, but also for Folder and Store objects, as well as for any other objects that have an EntryID property. This not only allows comparisons between a stored EntryID and the EntryID of a new object but also between different EntryID formats. When using Outlook with Exchange, you can retrieve two variations of the EntryID property. The short-term EntryID is only valid for that Outlook session and may change when Outlook is run again. The long-term EntryID is a permanent GUID that never changes as long as that item retains its EntryID. CompareEntryIDs enables you to compare IDs with both of those formats without concerning yourself with whether an ID is short-term or long-term. The CurrentProfileName property returns the current Outlook profile, which may be different from the default profile name that the Application,DefaultProfileName property returns. If the user has more than one Outlook profile, the current profile is the one that Outlook is logged in to. The SendAndReceive method is the equivalent of the Send/Receive All menu command in the user interface. This synchronous method sends and receives for all accounts defined in the current profile. For more granular sending and receiving, use the SyncObjects collection. Setting the optional ShowProgressDialog argument displays the send and receive progress dialog for the send and receive operation. Exchange Outlook 2003 introduced the ExchangeConnectionMode property for determining how Outlook connects to Exchange, which is necessary because of the cached connection modes introduced in 53
Slide 79: Chapter 3: Outlook Development Outlook 2003. Outlook 2007 adds three new properties for retrieving information about an Exchange server: ❑ ExchangeMailboxServerName returns the name of the Exchange server that hosts the default mailbox. If no Exchange server is used, a null string is returned. If Exchange is used, but Outlook is offline, an error is returned. ExchangeMailboxServerVersion returns the version of the Exchange server that hosts the ❑ default mailbox. If no Exchange server is used a null string is returned. If Exchange is used but Outlook is offline an error is returned, for example: If oNS.ExchangeConnectionMode <> OlExchangeConnectionMode.olOffline Then strServerName = oNS.ExchangeMailboxServerName strServerVersion = oNS.ExchangeMailboxServerVersion End If ❑ The AutoDiscoverXML property returns the XML string, which is used to discover an Exchange connection for Exchange 12. For connections to earlier versions of Exchange or where Exchange isn’t used, the property returns a null string. Categories Outlook categories have always been strings defined in specific items, stored as default categories hardcoded into Outlook or stored as a Master Category List in the registry. Outlook 2007 retains that definition for the Item.Categories property and adds a new Categories collection to the NameSpace object. Each Category object has properties to identify or set the category color, name, and shortcut key, as shown in the following code: Set colCategories = oNS.Categories Set oCategory = colCategories.Add(“Professional Programming Outlook 2007”, _ OlCategoryColor.olCategoryColorLightTeal, _ OlCategoryShortcutKey.olCategoryShortcutKeyNone) This code snippet adds a light teal book category to the Categories collection and makes it available to the user. Picking Folders The PickFolder dialog isn’t new to Outlook 2007, but the PickFolder dialog is useful anywhere your code needs the user to select a folder, for example as the target for moving an item. The function returns a Folder object if the user selected a folder, and Nothing (null in C#) if the user canceled the dialog. Dim oFolder As Outlook.Folder Dim oNS As Outlook.NameSpace Set oNS = Application.GetNameSpace(“MAPI”) Set oFolder = Nothing Set oFolder = oNS.PickFolder() If oFolder Is Nothing Then ‘user canceled the dialog 54
Slide 80: Chapter 3: Outlook Development Else ‘code to use the selected folder End If Picking Names The GetSelectNamesDialog function is the equivalent for the Outlook object model of the old CDO AddressBook dialog, which was never available in the Outlook object model. You have complete control over this dialog, unlike the PickFolder dialog, which you cannot customize. With the GetSelectNamesDialog dialog, you can control the dialog caption, the number of and labels for the recipient wells, the initial address list shown, and whether the user can select other address lists, whether only one or multiple recipients can be selected, the display mode, and whether to force resolution of recipients. The SetDefaultDisplayMode method enables you to tailor the dialog to modes such as selecting email, or meeting or task request recipients. The OlDefaultSelectNamesDisplayMode enumeration lists the default display modes that are available. In the following table, the column labeled as Wells refers to the recipient wells available in the dialog, such as To, Cc and Bcc. The column labeled Group refers to the ability to select more than one recipient in the dialog. Member olDefaultDelegates olDefaultMail olDefaultMeeting Caption Add Users Select Names Select Attendees and Resources Select Members Select Rooms Wells To To, Cc, Bcc To, Cc, Bcc Labels Add To, Cc, Bcc Required, Optional, Resources To Edit box To To, Cc, Bcc Required, Optional, Resources To Group Y Y Y olDefaultMembers To Y olDefaultPickRooms To Rooms Resource recipient To Y olDefaultSharingRequest olDefaultSingleName olDefaultTask Select Names Select Name Select Task Recipient To To Y To To To Y If you don’t set a display mode, the default olDefaultMail mode is used when the dialog is displayed. The SetDefaultDisplayMode method sets many of the GetSelectNamesDialog class’s properties, so if you plan to customize the dialog properties, do so after you call the SetDefaultDisplayMode method. 55
Slide 81: Chapter 3: Outlook Development Function SelectSignee() As Outlook.Recipient Dim oDialog As Outlook.SelectNamesDialog Dim colRecips As Outlook.Recipients Dim oRecip As Outlook.Recipient Set oRecip = Nothing Set oDialog = Application.Session.GetSelectNamesDialog With oDialog .SetDefaultDisplayMode olDefaultDelegates .AllowMultipleSelection = False .Caption = “Wrox” .ToLabel = “Sign Up:” .Display If .Recipients.Count > 0 Then Set oRecip = .Recipients.Item(1) End If End With If (oRecip Is Nothing) Then MsgBox “No recipient selected” Else MsgBox oRecip.Name End If Set SelectSignee = oRecip End Function Figure 3.1 shows the resulting GetSelectNamesDialog with the custom Sign Up button and Wrox dialog caption. The Sign Up button label is used in place of the default To label. Figure 3.1 56
Slide 82: Chapter 3: Outlook Development Summar y In this chapter, you learned more about the Application and NameSpace objects and some of their properties, methods, and events. You learned how to initialize global Application and NameSpace objects and make them available throughout your code, especially in COM addins. You also learned about additions to the Outlook object model such as Categories, context menus, user interface dialogs, and the ItemLoad event. The many new properties, methods and events added to the Application and NameSpace objects are key to developing in Outlook without having to use other APIs, but possibly the most important change for developers is the new more relaxed security model. If your Outlook objects are directly or indirectly derived from the trusted Application or Item objects passed to Outlook VBA, form code, or COM addins, the code you write is trusted by Outlook and won’t fire the security warnings that have been a problem since the Outlook Object Model Guard was introduced in Outlook 2000 SP2. This, along with the trust extended to external programs that run on computers with up-to-date antivirus protection, may be the biggest new features in Outlook 2007 development. In the next chapter, you start learning to put these pieces together in the Outlook VBA project to work with SyncObjects and Accounts, AddressBooks, Stores, Folders, and Items collections. 57
Slide 84: Outlook VBA In this chapter, you learn more about working with the Outlook VBA project, and how to use it to create macros and to prototype code destined for use in COM addins and standalone projects. The code in this chapter uses the intrinsic ThisOutlookSession class module, custom classes, and VBA UserForms for user input and data display to create macros that you can run on demand as well as code that runs automatically when Outlook starts or in response to specific Outlook events. These types of uses are common not only for VBA macro code but also in almost every Outlook application you write, including COM addins and standalone projects. The Outlook VBA Project Unlike Office applications such as Word, where the VBA project is document oriented, Outlook code is oriented towards the application. This orientation is similar to the orientation of COM addins and standalone projects, and makes Outlook VBA especially useful as a prototyping tool. The VBA project is also used for creating two classes of macros, macros that run automatically and those that are run on demand by the user, which you learn about in this chapter. The Project File Outlook has one global VBA project file, stored in the file system as VBAProject.OTM. This file is usually located in the C:\Documents and Settings\<windows logon>\Application Data\Microsoft\Outlook folder, where <windows logon> is the current user logon name for Windows. All Outlook VBA code is contained in this project file. This is unlike Word or Excel VBA projects, which are tied to the document or workbook and where you can have many different VBA projects.
Slide 85: Chapter 4: Outlook VBA ThisOutlookSession You learned in Chapter 1 about the ThisOutlookSession class module, which has special backing for Outlook’s application-wide events. This class is always present in all Outlook VBA projects and can be used for all of your macros and prototyping code. However, it’s usually better to group code into related functions contained in separate code and class modules. The ThisOutlookSession class is used in this chapter primarily to contain code that references the built-in Application events, and for startup code that initializes event-handling objects. Most other eventhandling code in this chapter is grouped into custom class modules, with examples shown of handling application-wide events in custom class modules. Macros and VBA Procedures Outlook VBA code uses three types of procedures: ❑ ❑ ❑ Functions, procedures that may take zero or more input arguments and return a value Subs, procedures that may take zero or more input arguments and do not return a value Macro Subs, procedures that take zero input arguments that are declared global in scope, either explicitly or implicit Macro Subs are special cases of standard Sub procedures. Only Subs with no input arguments that are declared globally can be macros. A Sub may be declared globally by using the Public keyword in the Sub declaration, or it may be declared as implicitly global by omitting any scope keyword in the Sub declaration. The following examples show the difference between macro and non-macro Sub declarations: Subs That Are Not Macros Private Sub Test() Sub Test(strValue As String) Public Sub Test(strValue As String) Subs That Are Macros Sub Test() Public Sub Test() Only macro Subs can be run from the Tools ➪ Macros menu list or from a custom toolbar button. Figure 4.1 shows the macro list opened from the Macros menu list. Running a macro from a custom toolbar button immediately executes the macro without opening a list for macro selection. Macro Security As you learned in Chapter 1, Outlook defaults to disabling all VBA code that isn’t signed. You learned how to create a code signing certificate and to sign code using the certificate with the Selfcert utility in Chapter 1. If you have not signed your code and do not want to set a lower security level you should go back to Chapter 1 now and create your code signing certificate and sign your VBA project. 60
Slide 86: Chapter 4: Outlook VBA Figure 4.1 Security Levels Select Tools ➪ Trust Center to open the new Trust Center dialog and select the Macro Security section to view the available security levels for Outlook VBA code. The security settings apply to all VBA code, whether or not it is macro code by strict definition. The following security levels are available for VBA code: ❑ ❑ ❑ ❑ No warnings and disable all macros Warnings for signed macros; all unsigned macros are disabled Warnings for all macros No security checks for macros (not recommended) If you don’t sign your code with a certificate created using Selfcert or with a code signing certificate issued by a recognized certificate authority such as VeriSign or Thawte, you can only run VBA code if you enable one of the last two security levels, which are not as secure as the first two security levels. The first, most secure, macro security level prevents all VBA code from running, so it’s not a setting you want to use if you are developing Outlook VBA code. Signed Macro Code Even with the recommended second security level set and signed code, you will still get warnings every time Outlook is opened. This quickly gets annoying, so to disable the startup macro warnings add your signature to the Trusted Publishers list. You learned how to do this in Chapter 1. Class Modules The code for this chapter uses both the ThisOutlookSession class and user classes you add to the VBA project. ThisOutlookSession is intrinsic to the Outlook VBA project and appears in all Outlook project files. 61
Slide 87: Chapter 4: Outlook VBA In general the best way to utilize ThisOutlookSession is to handle application-wide events in this class module and to instantiate objects and classes that will be used by other code in the project. If you add all your event-handling code and class-level code to ThisOutlookSession, it will quickly become packed with code and it will be hard to segregate the code into logically grouped sets of procedures and event handlers. ThisOutlookSession The code for this chapter that’s placed in ThisOutlookSession falls into three categories: initialization code, application-wide event handlers and initializations for event handlers that are in user classes. This shows a common mix of uses for code in ThisOutlookSession. The initialization code consists of module-level declarations and code in the Application_Startup event handler. This is the equivalent of code for a COM addin that’s in the OnConnection event handler, or for VSTO applications, code that’s in the ThisAddIn class in the Startup event handler. Module-level declarations are placed before any procedures or event handlers in ThisOutlookSession: Private WithEvents oInbox As Outlook.Folder ‘Inbox folder events Private WithEvents colInspectors As Outlook.Inspectors ‘Inspectors events Private oDeletedItems As Outlook.Folder ‘Deleted Items folder The WithEvents modifier for the declarations allows those objects to handle any events exposed for that type of object. Declaring variables at module level enables those variables to retain scope throughout the life of the module, unlike variables declared within procedures, which go out of scope when the procedure ends. ThisOutlookSession has scope for the lifetime of the Outlook session, so variables declared in this class at module level also have scope for the life of the Outlook session. User Classes The code for this chapter uses four user classes: ❑ ❑ ❑ ❑ clsCategorizeRule clsContactWrap clsMailWrap clsSearch To add a class module to the VBA project: 1. 2. Select Insert ➪ Class Module. Add four user classes now, and name them as indicated in the list above. You will use these classes and populate them with code later in this chapter. Classes that are inserted by the user can be used for any code that can be inserted in the ThisOutlookSession class. The main difference is that for user classes you must instantiate an Outlook.Application object declared WithEvents to be able to handle application-wide events. User classes have access to the same 62
Slide 88: Chapter 4: Outlook VBA intrinsic Application object that is available elsewhere in the Outlook VBA project. Another difference is that a user class must be instantiated, whereas ThisOutlookSession is automatically instantiated when Outlook and the VBA project start up. Code Modules The code in this chapter uses one code module for macros and for global object and variable declarations. Select Insert ➪ Module to insert a new code module in the VBA project. Name the code module Chapter_4. Add the code declarations shown in Listing 4.1 to the Chapter_4 code module. Listing 4.1 Public g_oNS As Outlook.NameSpace ‘global NameSpace object ‘to keep contact wrapper classes alive Public colContactWrapper As New Collection ‘to keep mail item wrapper classes alive Public colMailWrapper As New Collection Public cSearch As clsSearch ‘AdvancedSearch class Public cRule As clsCategorizeRule ‘Categorize Rule class Public g_lngID As Long These lines declare a global NameSpace object used throughout the code in this chapter, two collections, two classes, and a global Long. Long data types in VBA and VB 6 are 32-bit integers, equivalent to the Integer (int) data type in VB.NET and C#. The two collections, colContactWrapper and colMailWrapper, are used to maintain references to the clsEmailWrap and clsContactWrap classes and keep them alive and in scope. Using collections for this allows you to handle multiple contacts and emails that are open or accessed at the same time, and to separately handle the properties, methods, and events of those items without any interference from other open or accessed items of the same type. The code for clsEmailWrap is shown in Listing 4.13. The code for clsContactWrap is shown in Listing 4.14. In C#, you would add a reference to a wrapper class to a hashtable or List to keep the reference alive, just as collections are used for that purpose in VBA, VB 6, and VB.NET. Add one macro Sub now to the code in the code module. The code in Listing 4.2 is used to initialize a class that adds a custom menu item to the item context menu. This menu is displayed when you rightclick an item in a folder view in Outlook. 63
Slide 89: Chapter 4: Outlook VBA Listing 4.2 ‘Initialize the Categorize Rule class Public Sub ContextCategoryRule() Set cRule = New clsCategorizeRule End Sub This macro instantiates an instance of the clsCategorizeRule class as the global cRule object. Making this class object global to the VBA project gives it scope throughout the life of an Outlook session, from the time the macro is run until Outlook is closed. The code for the clsCategorizeRule class is shown in Listing 4.16. To add the context menu entry automatically when Outlook starts, add this line to in the Application_Startup code in Listing 4.4: Set cRule = New clsCategorizeRule Office UserForms VBA UserForms are the VBA equivalent of Windows Forms in .NET code or standard Forms in VB 6 code. UserForms can be used to get user input and also to display information or data gathered by procedural code elsewhere in the VBA project. UserForms are classes with a user interface component, so any code that can be used in a class module, such as event handling code, can also be used in a UserForm. All controls used in VBA UserForms are ActiveX controls; .NET form controls cannot be used in UserForms. The standard controls used in UserForms (labels, textboxes, command buttons, etc.) all come from the MS Forms 2.0 library, the same library that provides the controls for Outlook forms. Creating the Macro User Interface The code in this chapter uses two VBA UserForms, one for the automatic macro that checks for a Subject in all outgoing emails, and the other form for the macro to check today’s To-Do list. This section shows you how to add the two UserForms to the project. To add the two UserForms to the project, select Insert ➪ UserForm in the VBA project menu twice. The NoSubjectForm The first form, NoSubjectForm, shown in Figure 4.2, will be used by the procedure that runs every time an outgoing email is sent. This code, shown in Listing 4.13, checks for no Subject in the email, and displays the NoSubjectForm if the email is sent with no Subject. This macro is an example of an automatic macro that runs with no user intervention when circumstances are appropriate, in this case when an email is sent. 64
Slide 90: Chapter 4: Outlook VBA Figure 4.2 1. Set the properties of the form as shown in the following table. Form Property Name Caption Height Width Value NoSubjectForm Email With No Subject 141 72 2. Add an OptionButton control to the form. To add any control to a form, select the control on the Control Toolbox, and draw the selected control on the form. If the Control Toolbox isn’t displayed when the form is selected, display it by clicking the Toolbox icon on the toolbar or by selecting View ➪ Toolbox. Select the OptionButton control you just added by clicking it, and set its properties to those in the following table. OptionButton Property Name Caption Height Left Top Value Width 3. Value optNoSubject Send with no Subject 18 12 6 False 174 65
Slide 91: Chapter 4: Outlook VBA 4. Add another OptionButton, and set its properties as shown in the following table. OptionButton Property Name Caption Height Left Top Value Width Value optSubject Add a Subject 18 12 24 True 174 5. Add a Label control, and set its propertes as shown in the following table. Label Property Name Caption Height Left Top Width Value lblSubject Subject 18 12 54 42 6. Add a TextBox control, and set its properties to those in the following table. TextBox Property Name Height Left Top Width Value txtSubject 18 54 54 138 66
Slide 92: Chapter 4: Outlook VBA 7. Finally, add a CommandButton control, and set its properties as shown in the following table. CommandButton Property Name Caption Height Left Top Width Value cmdOK OK 18 69 90 72 The code in Listing 4.13 displays the NoSubjectForm when an email is sent with a blank Subject. The ToDoForm The second UserForm in this chapter, shown in Figure 4.3, is used to display a list of all tasks due for today that are listed in the To-Do Bar. This form is used in the Today’s ToDo List macro, shown in Listing 4.18, which is run on demand by the user. Figure 4.3 1. Set the form properties for the Today’s ToDo List form as shown in the following table. Form Property Name Caption Height Width Value ToDoForm Today’s ToDo List 180 240 67
Slide 93: Chapter 4: Outlook VBA 2. Add a Listbox control to the form and set its properties as shown in the following table. ListBox Property Name ColumnCount Height Left Top Width Value lstToDo 3 113 34 12 168 3. Add a CommandButton control to the form and set its properties as shown in the following table. CommandButton Property Name Caption Default Height Left Top Width Value cmdOK OK True 18 78 132 66 4. Save your work to make sure that the forms are available for use later in this chapter with the code for the macros. Wor king with Outlook Events Events in VBA can only be handled in classes or forms. Code modules cannot handle any events. Forms can handle events but are rarely used in VBA because forms are usually shown only for a brief time and then unloaded. A form could be hidden and still handle events, but in general most event handlers are located in class modules. Place the code in Listing 4.3 in the ThisOutlookSession class module, at the module level before any procedures in the class. 68
Slide 94: Chapter 4: Outlook VBA Listing 4.3 Private WithEvents oInbox As Outlook.Folder ‘Inbox folder events ‘Inspectors events Private WithEvents colInspectors As Outlook.Inspectors Private oDeletedItems As Outlook.Folder ‘Deleted Items folder The oInbox Folder object declared at the module level in ThisOutlookSession is declared WithEvents, meaning that it can handle events exposed for the Folder object. Two events are exposed for Folder objects in Outlook 2007, BeforeFolderMove and BeforeItemMove. These events are new to Outlook 2007. Code that uses the BeforeItemMove event is shown in Listing 4.10. The colInspectors Inspectors collection object also is declared WithEvents, and is used to handle the one exposed event for the Inspectors collection, NewInspector. This event fires when a new Inspector is opened, whenever a user opens a new or existing item. Code that uses the NewInspector event is shown in Listing 4.12. The oDeletedItems Folder object is also used in the BeforeItemMove event handler code. Application Events The Application events you’ll be working with in this chapter are: ❑ ❑ ❑ ❑ ❑ ❑ ❑ Application_Startup Application_ItemSend Application_ItemLoad Application_NewMailEx Application_ContextMenuClose Application_ItemContextMenuDisplay Application_AdvancedSearch User classes are used to handle the Application_ContextMenuClose, Application_ ItemContextMenuDisplay and Application_AdvancedSearch events. The other event handlers are coded in the ThisOutlookSession class module. Startup The Application_Startup event handler is automatically available to you in the ThisOutlookSession class module. To expose the event, select Application in the Object drop-down at the top left of the code window. Click the drop-down, which starts out showing (General) and select Application. In the righthand drop-down, select the Startup event to add a prototype of the Application_Startup event handler to the code. Add the code in Listing 4.4 to the Application_Startup event handler procedure in ThisOutlookSession. 69
Slide 95: Chapter 4: Outlook VBA Listing 4.4 Private Sub Application_Startup() ‘global NameSpace object Set g_oNS = Application.GetNamespace(“MAPI”) ‘Inbox (handles events) Set oInbox = g_oNS.GetDefaultFolder(olFolderInbox) ‘Deleted Items folder Set oDeletedItems = g_oNS.GetDefaultFolder(olFolderDeletedItems) ‘Inspectors collection (handles events) Set colInspectors = Application.Inspectors End Sub This code first instantiates the global NameSpace object declared in the Chapter_4 code module, making a NameSpace object available throughout the VBA project code. The oInbox Folder object declared at the module level is instantiated as the Inbox folder, using the NameSpace.GetDefaultFolder() method. This method accepts any of the OlDefaultFolders enumeration members, or the equivalent numeric values. The enumeration member for the Inbox is olFolderInbox. The GetDefaultFolder method is used with the olFolderDeletedItems enumeration member to instantiate the oDeletedItems Folder object as the Deleted Items folder. This object is instantiated once on startup to save time in the event handler code. Most Outlook events are not reentrant. If your code takes too long to execute in an event handler, you may miss the next instance of that event. When it comes time to optimize your code for production use, you should always make an effort to minimize the time you spend in your event handlers. Finally, the colInspectors Inspectors collection object is instantiated using the intrinsic Application.Inspectors collection. Every time Outlook starts up with macros enabled, the code in Application_Startup is executed, providing you with a way to run selected code on startup. ItemSend With Application selected in the Object drop-down, select the ItemSend event in the right-hand dropdown. You learned about Application_ItemSend in Chapter 3, with code that added a disclaimer to outgoing emails. The Application_ItemSend code in Listing 4.5 might be used in a corporate environment, where there’s a policy that no emails can be sent using private email addresses. This code uses the new Account object to see if the sending account is an Exchange server account. After making sure the outgoing item has a Class of olMail, an Account object is instantiated in the line Set oAccount = oMail.SendUsingAccount. 70
Slide 96: Chapter 4: Outlook VBA The AccountType property is checked to see if the sending account is an Exchange email account. Production code would check for and handle other outgoing item types such as meeting and task requests, and handle any possible errors. Listing 4.5 Private Sub Application_ItemSend(ByVal Item As Object, Cancel As Boolean) Dim oMail As Outlook.MailItem Dim oAccount As Outlook.Account Dim strMessage As String strMessage = “You must send emails using your Exchange email account” ‘if this is a mail item If Item.Class = olMail Then Set oMail = Item ‘get the account used to send the email Set oAccount = oMail.SendUsingAccount ‘check for not sending using an Exchange account If oAccount.AccountType <> olExchange Then ‘if not Exchange cancel the send Cancel = True MsgBox MsgBox strMessage, vbOKOnly + vbExclamation End If End If Set oMail = Nothing Set oAccount = Nothing End Sub Some properties related to sending aren’t populated when the ItemSend event fires; they are populated later when the transport, an email service such as POP3, Exchange, or IMAP populates them. Accessing properties such as MailItem.SenderEmailAddress or MailItem.SenderEmailType in ItemSend causes automation errors or returns blank values. The new SendUsingAccount object is populated and works very well in ItemSend; in fact, the default sending account is only populated in the Application .ItemSend and Item.Send events. At all other times, SendUsingAccount returns Nothing (null) if the default sending account is used. Other common uses for the ItemSend event are to add recipients to outgoing email such as new To, Cc, or Bcc recipients and to add or remove attachments from emails. ItemLoad You learned in Chapter 3 that the ItemLoad event fires when Outlook is loading an item into memory. This event fires whenever an item is accessed in any way, unless the item is already cached in memory. The code in Listing 4.6 checks to see if the loaded item is a contact item, and if so, it instantiates an instance of the contact wrapper class, clsContactWrap. Set clsContact = New clsContactWrap 71
Slide 97: Chapter 4: Outlook VBA The wrapper class’s internal ContactItem object is instantiated as the contact which is being loaded, and a Key value is set for that instance of the wrapper class. The global g_lngID is then incremented for the next use as a Key value. With clsContact .Contact = oContact .Key = CStr(g_lngID) End With The new contact wrapper is then added to the colContactWrapper collection, so its reference stays alive and in scope. Listing 4.6 Private Sub Application_ItemLoad(ByVal Item As Object) Dim clsContact As clsContactWrap Dim oContact As Outlook.ContactItem Dim sKey As String ‘if a contact is opened or read in a folder view If Item.Class = olContact Then ‘create an instance of the contact wrapper class Set oContact = Item sKey = CStr(g_lngID) Set clsContact = New clsContactWrap With clsContact .Contact = oContact .Key = CStr(g_lngID) End With ‘add the class to a collection to keep it alive colContactWrapper.Add clsContact, sKey g_lngID = g_lngID + 1 End If Set clsContact = Nothing Set oContact = Nothing End Sub The clsContactWrap code marks the contact as a task that is due next week if the loaded contact is marked as Private. This action automatically displays the contact task in the new To-Do Bar. The code for clsContactWrap is shown in Listing 4.14. NewMailEx The NewMailEx event is an excellent event to use when you need to process incoming items for purposes such as processing that you don’t want to do using the Rules Wizard, or that cannot be done using the Rules Wizard. This NewMailEx event handler scans incoming emails for those with a flag marked for follow-up. Any items with follow-up flags are marked as completed. 72
Slide 98: Chapter 4: Outlook VBA The code in this NewMailEx event handler in Listing 4.7 splits the comma-separated list of incoming EntryIDs into a string array. The array is iterated in a For loop, and each item is instantiated as an Object. This is done because an incoming Outlook item can also be a meeting request, a non-delivery report, a task request, or a number of other types of items. If the Object is a mail item, a MailItem object is instantiated from the Object, and the Mailitem’s PropertyAccessor is instantiated. The FlagStatus property is not directly available in the Outlook object model for mail items, so you need to access that property using the PropertyAccessor object. To access FlagStatus, you use the DASL property tag “http://schemas.microsoft.com/mapi/proptag/ 0x10900003”. The status is evaluated to see if the flag property is set by checking for the olFlagMarked value. If that value is found, the setting is changed to olFlagComplete and the item is saved to persist the change. Listing 4.7 Private Sub Application_NewMailEx(ByVal EntryIDCollection As String) Dim oItem As Object Dim oMail As Outlook.MailItem Dim oPropAccessor As Outlook.PropertyAccessor Dim oNs As Outlook.NameSpace Dim aryItems() As String Dim FlagStatus As Long Dim i As Long ‘property tag for FlagStatus, used for follow up flags Const PR_FLAG_STATUS As String = _ “http://schemas.microsoft.com/mapi/proptag/0x10900003” ‘EntryIDCollection is a comma separated list of EntryID’s aryItems = Split(EntryIDCollection, “,”) Set oNS = Application.GetNameSpace(“MAPI”) For i = LBound(aryItems) To UBound(aryItems) ‘iterate the array Set oItem = oNS.GetItemFromID(aryItems(i)) ‘Object If oItem.Class = olMail Then ‘look for mail items Set oMail = oItem ‘set MailItem from Object Set oPropAccessor = oMail.PropertyAccessor ‘read flag status property FlagStatus = oPropAccessor.GetProperty(PR_FLAG_STATUS) ‘if marked for follow up If FlagStatus = olFlagMarked Then ‘mark the flag completed oPropAccessor.SetProperty PR_FLAG_STATUS, olFlagComplete ‘save change oMail.Save End If End If Next i (continued) 73
Slide 99: Chapter 4: Outlook VBA Listing 4.7 (continued) Set oNS = Nothing Set oItem = Nothing Set oMail = Nothing Set oPropAccessor = Nothing End Sub AdvancedSearch The AdvancedSearch method is used to perform filtered searches of one or more folders in a Store, returning the results of the search in a Results collection. This is the same search that is performed in the user interface with the Advanced Find method. AdvancedSearch works by creating a temporary search folder that contains the Results collection from the search. This search folder is not visible to the user, and is discarded by Outlook when the search results go out of scope. Searching using AdvancedSearch has the same limitation as any search folder, the results can span only one Store object. Searching across multiple Stores is not supported. Each AdvancedSearch has filter, search scope and tag properties that are used to qualify the search. The SearchSubfolders’ Boolean property is used to modify the scope of the search to include or exclude subfolders of the scoped folders. The following table contains some examples of scoping the search. All scope arguments are strings, enclosed in single quotation marks. Scope ‘//Personal Folders’ ‘//Mailbox - Casey Slovak’ ‘Inbox’, ‘Sent Items’, ‘Drafts’ ‘Inbox’, ‘Sent Items’, ‘Drafts’ ‘Contacts’, ‘Inbox’ SearchSubfolders True True Folders searched The top of store of a PST file and all subfolders The top of store of an Exchange mailbox and all subfolders The Inbox, Sent Items, and Drafts and all of their subfolders The Inbox, Sent Items, and Drafts folders only True False False The Contacts and Inbox folders only Searches can work in different types of folders, as in the example above that searches in the Contacts and Inbox folders. However, a successful search across folder types will search only for properties that are common to the types of items that are in all the searched folders, such as Subject or Body. The filter used for the search must be a DASL syntax filter, as shown in Listing 4.8 for this example of finding incomplete tasks. The Completed property is represented in DASL syntax as “http://schemas .microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/811C000B”. Searching for tasks that aren’t complete would look for that property with a value of False in all scoped items. 74
Slide 100: Chapter 4: Outlook VBA Any item flagged as a task for the To-Do Bar uses the new task-related properties and will be returned by the search. Listing 4.8 Public Sub FindIncompleteTasks() Dim objSch As Outlook.Search Dim strFilter As String Dim strScope As String Dim strTag As String Dim Completed As String ‘Task completed Completed = “http://schemas.microsoft.com/mapi/id/“ Completed = Completed & _ “{00062003-0000-0000-C000-000000000046}/811C000B” strFilter = Completed & “ = ‘False’“ ‘Scope is entire store, either Personal Folders or Exchange mailbox. ‘Uncomment the following lines as appropriate for the store type strScope = “‘//Personal Folders’“ ‘strScope = “‘//Mailbox - Casey Slovak’“ strTag = “xxxyyy” ‘unique tag property Set cSearch = New clsSearch ‘new instance of search class cSearch.Tag = strTag ‘this allows the class to handle completion of this search Set objSch = Application.AdvancedSearch(Scope:=strScope, _ Filter:=strFilter, SearchSubFolders:=True, Tag:=strTag) Set objSch = Nothing End Sub For the code in Listing 4.8, remember to change the mailbox name to the name of your mailbox if you are testing the code with an Exchange profile or to uncomment the line strScope = “//Personal Folders’“ if you are testing it with a PST file profile. Change “Personal Folders” if your PST file has a different name. The Tag argument is used to identify which search was completed, which is useful when performing multiple searches at the same time, and to make sure the correct AdvancedSearchComplete event handles the results of the related search. The filter for a search can include multiple logical clauses, separated by the OR and AND operators. The NOT operator may also be used to construct a search filter. All filter clauses must use DASL syntax. For example, the following filter would add a clause to the previous filter that limits the search to only items that contain the MessageClass IPM.NOTE: strFilter = strFilter & _ “ AND http://schemas.microsoft.com/mapi/proptag/0x001A001E LIKE ‘IPM.Note’“ 75
Slide 101: Chapter 4: Outlook VBA This search returns standard and custom email items that have the IPM.Note base MessageClass and are marked as completed tasks. It will not return task items that aren’t complete. Using DASL syntax ,the MessageClass property is referenced as “http://schemas.microsoft.com/mapi/proptag/0x001A001E”. A good way to derive the DASL syntax for a filter is to use the view customization dialog. Select View ➪ Current View ➪ Customize to open the customization dialog, and click the Filter button. Use the Advanced tab to set up the filter using the available properties and logical operators, and then click the SQL tab to view the DASL syntax for the filter. The FindIncompleteTasks macro shown in Listing 4.8 can be run from the Macros dialog. The AdvancedSearchComplete event handling is done by the code in clsSearch. The line in FindIncompleteTasks, Set cSearch = New clsSearch instantiates an instance of the clsSearch, created in Listing 4.9. To ensure that the correct instance of the class handles the correct AdvancedSearchComplete event, the Tag property for the search is used to set the Tag property of clsSearch. Place the following code in the clsSearch class module you created earlier. Listing 4.9 Private WithEvents oOL As Outlook.Application ‘handles events Private m_sTag As String Public Property Let Tag(sTag As String) ‘the Tag lets us know we’re handling the correct ‘ AdvancedSearchComplete event m_sTag = sTag End Property Private Sub Class_Initialize() Set oOL = Application End Sub Private Sub Class_Terminate() Set oOL = Nothing End Sub Private Sub oOL_AdvancedSearchComplete(ByVal SearchObject As Search) Dim colResults As Outlook.Results Dim oItem As Object Dim strMessage As String Dim lngReply As Long Dim i As Long If SearchObject.Tag = m_sTag Then ‘check to see it’s the correct Tag Set colResults = SearchObject.Results ‘get the Results collection If colResults.Count > 0 Then strMessage = CStr(colResults.Count) & “ items were found.” _ & vbCrLf 76
Slide 102: Chapter 4: Outlook VBA Listing 4.9 (continued) strMessage = strMessage & “Do you want to open them?” lngReply = MsgBox(strMessage, vbInformation + vbOKCancel) If lngReply = vbOK Then ‘open the items if the user wants them opened For i = 1 To colResults.Count Set oItem = colResults.Item(i) oItem.Display Set oItem = Nothing Next End If Else strMessage = “No items were found.” MsgBox strMessage, vbInformation + vbOKOnly End If End If Set colResults = Nothing Set oItem = Nothing End Sub The Public Property Let Tag(sTag As String) line in the code establishes a public property that is set to the value of the Tag for the AdvancedSearch. This is compared to the SearchObject.Tag property passed in the AdvancedSearchComplete event handler, and if the tags match, the event handler code is executed. If SearchObject.Tag = m_sTag Then ‘check to see it’s the correct Tag A Results collection is instantiated from the SearchObject.Results collection and is checked to make sure that the collection isn’t empty. Set colResults = SearchObject.Results ‘get the Results collection If colResults.Count > 0 Then The user is then asked if he or she wants to open the items returned by the search, and if the answer is yes, the Results collection is iterated through in a loop, with each item in the collection being opened for viewing. The AdvancedSearch object was added to the Outlook object model in Outlook 2002, so it can be used in any Outlook code that needs to support Outlook 2002 or later. Folder Events Outlook 2007 added two new events for the Folder object. Previous versions of Outlook have no events exposed for the MAPIFolder object. The two new events are BeforeItemMove and BeforeFolderMove. Both work in similar fashion, firing if an item or folder is being moved or deleted. The fact that the event fires before the item or folder is moved or deleted and has a Cancel argument means that you now can finally trap all deletions and cancel them or take action based on which items are deleted. This is something that Outlook developers have been requesting for years, and finally have in Outlook 2007. 77
Slide 103: Chapter 4: Outlook VBA BeforeItemMove In previous versions of Outlook, there was no event for knowing that an item was deleted unless the item was opened and the File ➪ Delete menu selection was used. Only under those circumstances does the BeforeDelete event fire. If an item was deleted from a folder view, the only way to identify the item was to handle the ItemAdd event of the Items collection of the Deleted Items folder. If an item was hard deleted (using Shift+Delete to delete the item and bypass the Deleted Items folder) or if more than 16 items were deleted at once, you wouldn’t know about it. The ItemRemove event of the Items collection doesn’t pass a reference to what was deleted, so all it’s good for is telling you that something was deleted from a folder, not what was deleted. There are workarounds, such as maintaining a table of items located in the current folder and seeing if any are missing at timed intervals. But those workarounds are ugly hacks, are very inefficient, and only let you know that something was deleted. There’s no way to cancel the deletion, as there is with the limited BeforeDelete event. Place the following code for Listing 4.10 in the ThisOutlookSession module to handle deletions or items being moved in the Inbox folder. The oInbox object was declared WithEvents at the module level in ThisOutlookSession and was instantiated in the Application_Startup event in Listing 4.4. Listing 4.10 Private Sub oInbox_BeforeItemMove(ByVal Item As Object, _ ByVal MoveTo As MAPIFolder, Cancel As Boolean) Dim lngReply As Long Dim strMessage As String Dim blnDeleted As Boolean strMessage = _ “Are you sure you want to delete this item without reading it?” blnDeleted = False If Item.Class = olMail Then ‘mail items only If (MoveTo Is Nothing) Then ‘if hard deleted the target folder is Nothing blnDeleted = True ElseIf g_oNS.CompareEntryIDs(MoveTo.EntryID, _ oDeletedItems.EntryID) Then ‘moved to Deleted Items folder blnDeleted = True End If If blnDeleted Then If Item.UnRead Then ‘check UnRead status lngReply = MsgBox(strMessage, vbExclamation + vbYesNo) If lngReply = vbNo Then ‘cancel the deletion if user says to. ‘works for hard deletes too. Cancel = True 78
Slide 104: Chapter 4: Outlook VBA Listing 4.10 (continued) End If End If End If End If End Sub Three arguments are passed in the BeforeItemMove event handler: Item, MoveTo, and Cancel. The first two arguments are ByVal, meaning the objects passed are local copies of the original objects. The Cancel argument is passed ByRef, meaning that any changes to that Boolean value affect the original item and can be used to cancel the move operation. The BeforeItemMove and BeforeFolderMove events do not fire as a result of actions taken in autoarchiving or synchronizing operations; they do fire in response to code or the user moving or deleting objects. When any item is moved or deleted in the Inbox folder, the target folder is passed as the MoveTo argument to the BeforeItemMove event handler. If the item is being hard deleted (Shift+Delete), an action that bypasses the Deleted Items folder, the value of MoveTo is Nothing or null. The code tests for deletions only, using the following two lines: If (MoveTo Is Nothing) Then ElseIf g_oNS.CompareEntryIDs(MoveTo.EntryID, oDeletedItems.EntryID) Then The first line checks to see if MoveTo is Nothing, which indicates that the item was hard deleted. The second line compares the EntryID properties for the MoveTo folder and the Deleted Items folder, and if the values are the same the item is moved to the Deleted Items folder. The new CompareEntryIDs method is used to compare the EntryIDs. Comparing EntryID values using equality operators can produce unexpected errors. An EntryID for an object can be short or long term. Short-term EntryIDs are valid only for that session; long-term EntryIDs are permanent. Some Store providers, such as the provider for PST files, only use long-term EntryID values. Some, such as the Exchange transport, use both types of EntryIDs. The two properties are usually different lengths and will return not equal in comparison operations. The CompareEntryIDs method takes account of the differences and will return equal as the result of a comparison between short and longterm EntryIDs representing the same object. The code then checks the UnRead property of the mail item to see if the item is unread and prompts the user to see if he or she really wants to delete an unread item. Although the event fires when an item is hard deleted and can be canceled even in that case, the prompt from Outlook about permanently deleting the item will be displayed before the BeforeItemMove code is called. BeforeFolderMove The BeforeFolderMove event fires when a folder is moved or deleted. It works the same as the BeforeItemMove event but only passes two arguments, MoveTo and Cancel: BeforeFolderMove(MoveTo As Folder, Cancel As Boolean) 79
Slide 105: Chapter 4: Outlook VBA The BeforeFolderMove event does not fire if a subfolder of the folder that’s being monitored is moved or deleted. Only moving or deleting the monitored folder will fire this event. User Events Code in the Outlook VBA project can not only handle Outlook events but also can be used to create or handle user-defined events. User events are created in classes or forms and can be handled by event handlers in classes or forms. The code for NoSubjectForm in Listing 4.11 demonstrates creating a user event; the event is handled in the code for the clsMailWrap class, shown in Listing 4.13. Code for the NoSubjectForm Place the following code in the NoSubjectForm. To place code in a form, right-click the form in the Project Explorer and select View Code. The code in Listing 4.11 works in conjunction with the code in the clsMailWrap class module to handle cases where emails are sent out with no subjects. The clsMailWrap class module code is shown in Listing 4.13. The form event is declared in the line Public Event DataReady(ByVal sSubject As String), which declares a DataReady event that passes a subject string when it fires. Firing the event is the job of the line RaiseEvent DataReady(txtSubject.Text), which notifies any code that has subscribed to the event that data is ready for reading. The RaiseEvent keyword is used to cause the firing of the event. Listing 4.11 Public Event DataReady(ByVal sSubject As String) Private Sub cmdOK_Click() RaiseDataReadyEvent ‘fire DataReady event Unload Me End Sub Private Sub optNoSubject_Click() txtSubject.Enabled = False txtSubject.Text = “” End Sub Private Sub optSubject_Click() txtSubject.Enabled = True txtSubject.SetFocus End Sub Private Sub RaiseDataReadyEvent() ‘this event is handled externally to the form ‘it passes the new subject text RaiseEvent DataReady(txtSubject.Text) End Sub The NoSubjectForm uses option buttons to let the user select whether to send the email with no subject or to add a subject to the outgoing email. The text box on the form is used to enter the subject, which is then passed to the DataReady event handler when the user clicks the OK button on the form. 80
Slide 106: Chapter 4: Outlook VBA Wrapper Classes and Collections Wrapper classes and collections are widely used in Outlook programming to handle the events for various Outlook objects and collections. The collections are used to store instances of the wrapper classes, keeping them alive until they are no longer needed, when the wrapper classes are removed from the collection and released. The question of the life of an object is something that is a concern to all programmers but especially so for .NET programmers. COM code automatically keeps class references alive until the classes are released or go out of scope. In .NET code, you have to pay special attention to keeping references alive so that they aren’t released by the running of the garbage collector. Adding a class reference to a collection, or in the case of C# code to a List or hashtable, ensures that the references are kept alive until they are no longer needed. Using the NewInspector Event to Instantiate a Wrapper Class The Inspectors collection has one event, NewInspector. This event fires when any item is opened and provides a handle to the new Inspector object. As you learned in Chapter 3, Inspectors are the windows used to view Outlook items, while Explorers are the windows used to view Outlook folders. You previously declared an Inspectors collection object WithEvents in the ThisOutlookSession class module, in Listing 4.3. Private WithEvents colInspectors As Outlook.Inspectors ‘Inspectors events Place the code in Listing 4.12 in ThisOutlookSession to handle any new Inspector objects that are opened. Listing 4.12 Private Sub colInspectors_NewInspector(ByVal Inspector As Inspector) Dim clsMail As clsMailWrap Dim oMail As Outlook.MailItem Dim sKey As String ‘if a mail item is opened in a new Inspector If Inspector.CurrentItem.Class = olMail Then ‘create an instance of the email wrapper class Set oMail = Inspector.CurrentItem sKey = CStr(g_lngID) Set clsMail = New clsMailWrap With clsMail .Email = oMail .Key = CStr(g_lngID) End With ‘add the class to a collection to keep it alive colMailWrapper.Add clsMail, sKey g_lngID = g_lngID + 1 (continued) 81
Slide 107: Chapter 4: Outlook VBA Listing 4.12 (continued) End If Set clsMail = Nothing Set oMail = Nothing End Sub When the NewInspector event fires, the Inspector.CurrentItem.Class property is checked to see if the item is a MailItem. If so, an instance of clsMailWrap is instantiated and the Email and Key properties of the wrapper class are set: Set oMail = Inspector.CurrentItem sKey = CStr(g_lngID) Set clsMail = New clsMailWrap With clsMail .Email = oMail .Key = CStr(g_lngID) End With The wrapper class is then added to the global colMailWrapper collection, using the same key property set in the wrapper class as the Key for the collection item. This makes it very easy later for the class to dereference itself and remove itself from the collection when it’s time for the class to be released. Email Wrapper The code in Listing 4.13 for the email wrapper class should be placed in the clsMailWrap class module. Listing 4.13 Private WithEvents m_oMail As Outlook.MailItem ‘handles mail item events ‘we are handling DataReady event from the form Private WithEvents m_UserForm As NoSubjectForm ‘the key lets us access this class in the collection Private m_Key As String Public Property Let Email(oMail As Outlook.MailItem) Set m_oMail = oMail ‘instantiate the mail item End Property Public Property Get Email() As Outlook.MailItem Set Email = m_oMail End Property Public Property Let Key(sKey As String) m_Key = sKey ‘set the key End Property Public Property Get Key() As String Key = m_Key 82
Slide 108: Chapter 4: Outlook VBA Listing 4.13 (continued) End Property Public Sub Dispose() ‘release module level objects and remove from the wrapper collection Set m_UserForm = Nothing Set m_oMail = Nothing colMailWrapper.Remove m_Key End Sub ‘handle the Send event for the mail item Private Sub m_oMail_Send(Cancel As Boolean) If m_oMail.Subject = “” Then ‘if Subject is blank Set m_UserForm = New NoSubjectForm m_UserForm.Show vbModal ‘see if the user wants to add a Subject End If End Sub Private Sub m_oMail_Unload() Call Dispose End Sub Private Sub m_UserForm_DataReady(ByVal sSubject As String) ‘we get the Subject from the form, passed by the raised form event. ‘we get this event before m_oMail_Send finishes so any Subject we add ‘ gets added to the mail item before it goes out. m_oMail.Subject = sSubject End Sub The code in this class declares two objects WithEvents so that they can be used to handle events fired by those types of objects: Private WithEvents m_oMail As Outlook.MailItem ‘handles mail item events Private WithEvents m_UserForm As NoSubjectForm Declaring a MailItem for event handling allows you to handle all MailItem-generated events. The WithEvents declaration for an instance of the NoSubjectForm allows you to handle the DataReady event that the form generates. The MailItem events being handled in this mail wrapper are the Send and Unload events. Other events exposed by a MailItem can also be handled by adding event handler code for those events. The nice thing about these wrappers is they enable you to separately handle events for many different items that are open at the same time. Only the events related to that item are fired in the wrapper class, so each wrapper can work with only its own item and not be concerned about any other open items. Public Property Let Email(oMail As Outlook.MailItem) Set m_oMail = oMail ‘instantiate the mail item End Property After m_oMail is set in the Email property procedure, the code in the wrapper class can handle any exposed events that fire, and only the events for that specific item will fire in the wrapper class. 83
Slide 109: Chapter 4: Outlook VBA The Unload event is used to know when to release the wrapper class and remove it from the wrapper collection. The Close event can also be used for this purpose. When Unload fires, the Dispose procedure is called: Public Sub Dispose() ‘release module level objects and remove from the wrapper collection Set m_UserForm = Nothing Set m_oMail = Nothing colMailWrapper.Remove m_Key End Sub The Dispose procedure releases the class’s module-level objects, m_UserForm and m_oMail, by setting them to Nothing, then the code removes the wrapper class from the wrapper collection by using the Key property as the index for the collection’s Remove method. This provides a very neat way for the class to clean up after itself. When the Send event is fired, the following code is executed: Private Sub m_oMail_Send(Cancel As Boolean) If m_oMail.Subject = “” Then ‘if Subject is blank Set m_UserForm = New NoSubjectForm m_UserForm.Show vbModal ‘see if the user wants to add a Subject End If End Sub The code tests the email Subject to see if it’s blank — a null string. If so, it opens a modal instance of the NoSubjectForm to let the user decide whether to send the email with no Subject or to add a Subject. The form must be opened modally, which suspends execution of the Send event code until the NoSubject form is closed. This is very important; otherwise, the Send event would continue execution and the email would be sent by the time the user decided what to do about the blank Subject. Finally, the code in the wrapper class handles the NoSubjectForm‘s DataReady event, which is fired when the user clicks the OK button in the form. This event handler fires before the form is closed, and before the Send event code returns to finish up the Send procedure. The Subject property of the email is set to the subject string passed by the DataReady event. Then the event handler code is executed and the form closes. Finally, the Send event finishes and the email is sent. Private Sub m_UserForm_DataReady(ByVal sSubject As String) ‘we get the Subject from the form, passed by the raised form event. ‘we get this event before m_oMail_Send finishes so any Subject we add ‘ gets added to the mail item before it goes out. m_oMail.Subject = sSubject End Sub You can use these wrapper classes and collections for any cases where multiple instances of Outlook objects can be opened at any one time. All such classes follow a similar pattern: 1. 2. 84 Create a class that wraps the object of interest and that handles all events of interest fired by that object. Create properties in the class for setting the object and a key property, plus any other properties that need to be set from outside the class or that need to be available to other code outside the class.
Slide 110: Chapter 4: Outlook VBA 3. 4. 5. When an object of interest is opened, create a new instance of the class and populate its object and key properties, as well as any other properties that need to be set on class startup. Add the new class instance to a collection that was declared with scope that keeps the collection alive for the duration of the application. This keeps the class alive as long as it’s needed. When the object is closed or no longer needed, release all internal class objects and use the Key property to remove the class from the collection, allowing the class to be released as it goes out of scope. Contacts Wrapper Class for Mark as Task Place the code for the contacts wrapper class shown in Listing 4.14 in the clsContactWrap class module created earlier in this chapter. An instance of this class is created whenever a contact is loaded by Outlook, in the Application_ItemLoad event handler. The Application_ItemLoad code is shown in Listing 4.6. Listing 4.14 Private WithEvents m_oContact As Outlook.ContactItem ‘handles contact events Private m_Key As String ‘the key lets us access this class in the collection Public Property Let Contact(oContact As Outlook.ContactItem) Set m_oContact = oContact ‘instantiate the contact End Property Public Property Get Contact() As Outlook.ContactItem Set Contact = m_oContact End Property Public Property Let Key(sKey As String) m_Key = sKey ‘set the key End Property Public Property Get Key() As String Key = m_Key End Property ‘release module level objects and remove from wrapper collection Public Sub Dispose() Set m_oContact = Nothing colContactWrapper.Remove m_Key End Sub Private Sub m_oContact_PropertyChange(ByVal Name As String) On Error Resume Next ‘if the Sensitivity property value is changing... If Name = “Sensitivity” Then With m_oContact (continued) 85
Slide 111: Chapter 4: Outlook VBA Listing 4.14 (continued) If .Sensitivity = olPrivate Then ‘mark private items for follow up in the ToDo Bar ‘set as a ToDo for this week If Not .IsMarkedAsTask Then .MarkAsTask olMarkThisWeek .TaskSubject = “Follow up with this contact: “ & .FullName .TaskStartDate = Date ‘due 7 days from now .TaskDueDate = DateAdd(“d”, 7, Date) End If Else ‘if no longer private mark as completed If .IsMarkedAsTask Then .TaskCompletedDate = Date End If End If End With End If End Sub ‘when the contact is unloaded from Outlook cache Private Sub m_oContact_Unload() Call Dispose End Sub When a contact is loaded the following code is executed to create a new instance of the clsContactWrap class. The code then sets the Contact and Key properties of the class to the newly loaded contact and the current key value. The new class is then added to the colContactWrapper collection to keep the class alive while the contact remains loaded. Set clsContact = New clsContactWrap With clsContact .Contact = oContact .Key = CStr(g_lngID) End With ‘add the class to a collection to keep it alive colContactWrapper.Add clsContact, sKey The class code in Listing 4.14 handles two contact events, PropertyChange and Unload. The Unload event handler is used the same way that the Unload handler is used in the email wrapper class code, to release the module-level ContactItem object and to remove that instance of the class from the colContactWrapper collection. The PropertyChange event fires for any Outlook item when any built-in property is changed, such as changes to the Subject or Body of an item. User-defined properties fire the CustomPropertyChange event when any user-defined property value is changed. 86
Slide 112: Chapter 4: Outlook VBA The PropertyChange event passes the name of the property being changed to the event handler; in this case, the Sensitivity property is being monitored for changes. Sensitivity is the property that sets an item as Normal, Private, Personal, or Confidential to restrict access to the item in contexts where Outlook data is shared, such as Exchange server. The code checks the Name property, which is passed as an argument to the event handler and which contains the name of the property being changed. If Name = “Sensitivity” the code checks to see if Sensitivity is being set as Private. If so, the new task properties that allow an item to be shown in the To-Do Bar are set. If Name = “Sensitivity” Then With m_oContact If .Sensitivity = olPrivate Then ‘mark private items for follow up in the ToDo Bar ‘set as a ToDo for this week If Not .IsMarkedAsTask Then .MarkAsTask olMarkThisWeek .TaskSubject = “Follow up with this contact: “ & .FullName .TaskStartDate = Date ‘due 7 days from now .TaskDueDate = DateAdd(“d”, 7, Date) End If The IsMarkedAsTask property is read to see if the item is already marked as a task. If not, the MarkAsTask property is set to olMarkThisWeek, which sets the flag used to display the item in the Next Week section of the To-Do Bar. A subject is set for the task, using the ContactItem.FullName property with an indication to follow up with this contact. The TaskStartDate property is set to 7 days from the current date, using the VBA DateAdd function to add 7 days to the current date. The follow-up flagging is indicated in the InfoBar of the open contact, as shown in Figure 4.4. Figure 4.4 The new task properties set on the contact ensure that the follow-up is also shown in the To-Do Bar, as a follow-up to do in the next week, with a due date 1 week from the current date, as shown in Figure 4.5. 87
Slide 113: Chapter 4: Outlook VBA Figure 4.5 If Sensitivity is set to something other than Private (olPrivate) the contact is checked to see if it’s marked as a task using the IsMarkedAsTask property, and if it is, the task is marked as complete with a completed date of the current date. If .Sensitivity = olPrivate Then Else ‘if no longer private mark as completed If .IsMarkedAsTask Then .TaskCompletedDate = Date End If End If When the task is marked as completed, the InfoBar in the open contact is changed to show the follow-up as completed, as shown in Figure 4.6. The completed task is automatically removed from the To-Do Bar list. Figure 4.6 Macro Projects The code in the next two sections of this chapter show how to work with the new Rules collection added to the Outlook 2007 object model. The code in Listing 4.15 shows how to use a macro to create a new rule, and the code in Listing 4.16 shows how to use a macro that runs automatically to create a context menu entry that creates a new rule. 88
Slide 114: Chapter 4: Outlook VBA Custom Rules Creating custom rules looks very complex when you first look at the Rules object model. Custom rules aren’t really that hard to understand, however; they follow the model used in the Rules Wizard: 1. 2. 3. 4. Setting the rule as either a send or receive rule. Send rules run after an item is sent; receive rules run after an item is received. This is the same as using a blank template in the Rules Wizard and selecting the option to use a send or receive template. One or more conditions used to test items to see if the rule should be run on them. One or more actions to take if the item meets the rule condition or conditions, such as moving an item or playing a sound. Zero or more exceptions that prevent the rule from running on a specific item based on the exception condition or conditions. Each rule also has a name and can be enabled or disabled, and can be run on demand when needed. Family Rule The Family rule is a receive rule that looks at the sender of an email. If the email was sent by one of a group of specific people, the email is moved to a Family folder that’s a subfolder of the Inbox. This rule has two exceptions: If the name “Irene” is used in the subject or text of the email or if the email if Low importance, the email isn’t moved to the Family folder. If the Family folder doesn’t exist, it’s created as a subfolder of the Inbox. Place the code in Listing 4.15 in the Chapter_4 code module you created earlier in this chapter. Listing 4.15 Public Sub CreateRule() Dim colRules As Outlook.Rules Dim oRule As Outlook.Rule Dim colActions As Outlook.RuleActions Dim oMoveAction As Outlook.MoveOrCopyRuleAction Dim oToCondition As Outlook.ToOrFromRuleCondition Dim oExceptImportance As Outlook.ImportanceRuleCondition Dim oExceptText As Outlook.TextRuleCondition Dim oInbox As Outlook.Folder Dim oTarget As Outlook.Folder Dim blnFound As Boolean On Error Resume Next Set oInbox = g_oNS.GetDefaultFolder(olFolderInbox) ‘See if target folder exists, create it if not Set oTarget = oInbox.Folders.Item(“Family”) If oTarget Is Nothing Then Set oTarget = oInbox.Folders.Add(“Family”, olFolderInbox) End If (continued) 89
Slide 115: Chapter 4: Outlook VBA Listing 4.15 (continued) ‘Get Rules collection from DefaultStore object Set colRules = g_oNS.DefaultStore.GetRules() blnFound = False For Each oRule In colRules ‘check for the Family rule If oRule.Name = “Family” Then blnFound = True ‘if the rule already exists just make sure it’s enabled. If Not oRule.Enabled Then oRule.Enabled = True End If End If Next If Not blnFound Then ‘Add a receive rule to Rules collection. ‘Rules are either send or receive rules. Set oRule = colRules.Create(“Family”, olRuleReceive) ‘Every rule has one or more conditions and one or more actions ‘Using a ToOrFromRuleCondition object Set oToCondition = oRule.Conditions.From With oToCondition .Enabled = True ‘sent to one of the following recipients .Recipients.Add “Ken Slovak” .Recipients.Add “Casey Slovak” .Recipients.ResolveAll End With ‘Using a MoveOrCopyRuleAction object Set oMoveAction = oRule.Actions.MoveToFolder With oMoveAction .Enabled = True .Folder = oTarget End With ‘A rule may have one or more exceptions. ‘Exception conditions are standard rule conditions. ‘Add rule exceptions: ‘1. Don’t move if low importance. ‘or ‘2. body/subject contains specified text. ‘Using an ImportanceRuleCondition object Set oExceptImportance = oRule.Exceptions.Importance With oExceptImportance .Enabled = True 90
Slide 116: Chapter 4: Outlook VBA Listing 4.15 (continued) .Importance = olImportanceLow End With ‘Using a TextRuleCondition object Set oExceptText = oRule.Exceptions.BodyOrSubject With oExceptText .Enabled = True ‘condition words or phrases are in a comma separated array .Text = Array(“Irene”) End With End If ‘Save the new rule. colRules.Save ‘Uncomment this line to execute the rule after saving it. ‘oRule.Execute End Sub The first thing the code does is get a reference to the Inbox folder and check to see if the Family folder exists. If the folder doesn’t exist, it’s created. Set oInbox = g_oNS.GetDefaultFolder(olFolderInbox) ‘See if target folder exists, create it if not Set oTarget = oInbox.Folders.Item(“Family”) If oTarget Is Nothing Then Set oTarget = oInbox.Folders.Add(“Family”, olFolderInbox) End If Setting the target folder, oTarget, is done by using the Folders collection of the Inbox folder. Every folder has a Folders collection, holding all subfolders of that folder. If the Family folder doesn’t exist, oTarget is Nothing, and an error is thrown. The On Error Resume Next statement at the beginning of the executable code in the macro allows code execution to continue, which enables you to test to see if the folder exists and to create the folder if not. If that error handler statement wasn’t in the code, an error message would be displayed if Family doesn’t already exist. When you create a new folder you can only create folders of certain existing types. Those types are indicated with enumeration values named for the folder type. Using the value olFolderInbox doesn’t create a new Inbox folder; that type is used for any email folder you create in code. Every Store has a Rules collection. In this case you’re working with incoming emails, so you’re using the Rules collection of the DefaultStore object of the global NameSpace object. Set colRules = g_oNS.DefaultStore.GetRules() After you have a Rules collection, the code iterates through the collection to see if a Rule with the Name “Family” exists. If that Rule exists, the Rule is enabled; if the Rule doesn’t exist, a flag is set so that the Rule is created. 91
Slide 117: Chapter 4: Outlook VBA blnFound = False For Each oRule In colRules ‘check for the Family rule If oRule.Name = “Family” Then blnFound = True ‘if the rule already exists just make sure it’s enabled. If Not oRule.Enabled Then oRule.Enabled = True End If End If Next The Create method of the Rules collection is used to create a new Rule object. Create is called with the name of the new Rule and a value indicating if the Rule is a send or receive Rule. If Not blnFound Then ‘Add a receive rule to Rules collection. ‘Rules are either send or receive rules. Set oRule = colRules.Create(“Family”, olRuleReceive) The Family rule uses a From RuleCondition that’s assigned to a ToOrFromRuleCondition RuleCondition. Not all conditions that are supported in the Rules Wizard are exposed in the RuleConditions object. Those conditions that are supported have defined RuleCondition properties available in the RuleConditions object. Two recipients are added to the ToOrFromRuleCondition and then resolved. When multiple condition tests are added to a RuleCondition the tests are OR’d together. In this case, the condition is true if the sender of the email was Ken Slovak OR Casey Slovak. ‘Using a ToOrFromRuleCondition object Set oToCondition = oRule.Conditions.From With oToCondition .Enabled = True ‘sent to one of the following recipients .Recipients.Add “Ken Slovak” .Recipients.Add “Casey Slovak” .Recipients.ResolveAll End With A Rule can have more than one RuleCondition. To add additional conditions to a Rule, declare one or more additional types and set their properties and enable the conditions. Multiple conditions are AND’d together, so all conditions must be true for the rule to be executed. If the rule exception for importance was added to the existing rule condition, the logic of the rule would be: If the email ((was sent by (Ken Slovak OR Casey Slovak)) AND (the Importance is Low)) then move the email to the Family folder. ‘adding an additional condition Set oExceptImportance = oRule.Conditions.Importance 92
Slide 118: Chapter 4: Outlook VBA The action for the Family rule is to move the email to a folder, the Family folder. A MoveToFolder action is added to the rule, and the target folder is set to oTarget. Multiple actions can be added to a rule, in which case the actions are all performed. ‘Using a MoveOrCopyRuleAction object Set oMoveAction = oRule.Actions.MoveToFolder With oMoveAction .Enabled = True .Folder = oTarget End With The line .Folder = oTarget illustrates a subtle distinction in Outlook syntax. No Set keyword is used to set the Folder object property to oTarget because you aren’t instantiating an object reference; you are setting a property that’s an object. In earlier versions of Outlook, this distinction wasn’t always followed, so setting Explorer.CurrentFolder uses the Set keyword. Two exceptions are added to the rule: 1. 2. An Importance condition with its Importance property set to Low A TextRuleCondition that tests the contents of the Subject and Body of the email for the name “Irene” ‘Add rule exceptions: ‘1. Don’t move if low importance. ‘or ‘2. body/subject contains specified text. ‘Using an ImportanceRuleCondition object Set oExceptImportance = oRule.Exceptions.Importance With oExceptImportance .Enabled = True .Importance = olImportanceLow End With ‘Using a TextRuleCondition object Set oExceptText = oRule.Exceptions.BodyOrSubject With oExceptText .Enabled = True ‘condition words or phrases are in a comma separated array .Text = Array(“Irene”) End With End If Every condition, action and exception for a rule must be enabled for that test to be applied by the rule. The rule itself can be enabled or disabled to determine whether or not the rule is run. Rules can be executed on demand by calling the Execute method of the rule. Figure 4.7 shows the Rules Wizard dialog with the Family rule that was just created, with the Family subfolder of the Inbox shown in the Mail Folders section of the Navigation Pane. 93
Slide 119: Chapter 4: Outlook VBA Figure 4.7 Context Menu Rule The context menu rule is an example of creating a rule on the fly, using information received when a context menu is opened. The code for this rule also demonstrates how to add your own menu item to a context menu, in this case the ItemContextMenu, where a new context menu entry to categorize all emails from the sender as Business is created. The code is initiated by running the ContextCategoryRule macro created in Listing 4.2. If you added the line Set cRule = New clsCategorizeRule to the Application_Startup code in Listing 4.4, running the macro is unnecessary; the context menu item will display automatically when the ItemContextMenu displays. ‘Initialize the Categorize Rule class Public Sub ContextCategoryRule() Set cRule = New clsCategorizeRule End Sub Place the code in Listing 4.16 in the clsCategorizeRule class module you created earlier in this chapter. Listing 4.16 ‘handles Click event Private WithEvents oButton As Office.CommandBarButton Private oBar As Office.CommandBar ‘context menus are CommandBar objects ‘handles context menu events Private WithEvents oOL As Outlook.Application Private m_sTag As String Private m_EntryID As String 94
Slide 120: Chapter 4: Outlook VBA Listing 4.16 (continued) Public Property Let Tag(sTag As String) m_sTag = sTag End Property Private Sub Class_Initialize() Set oOL = Application End Sub Private Sub Class_Terminate() ‘release module level objects Set oButton = Nothing Set oBar = Nothing Set oOL = Nothing End Sub ‘this is the meat, this event fires ‘when our context menu item is clicked Private Sub oButton_Click(ByVal Ctrl As Office.CommandBarButton, _ CancelDefault As Boolean) Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim oMail As Outlook.MailItem colRules As Outlook.Rules oRule As Outlook.Rule oRuleCondition As Outlook.AddressRuleCondition oRuleAction As Outlook.AssignToCategoryRuleAction colCategories As Outlook.Categories oCategory As Outlook.Category sName As String sEmail As String sCategories As String aryEmail(0) As String aryCategories(0) As String Set oMail = g_oNS.GetItemFromID(m_EntryID) ‘get the context item If Not (oMail Is Nothing) Then sEmail = oMail.SenderEmailAddress ‘get the sender address ‘name for the new rule we’re creating sName = “Categorize “ & sEmail & “ as Business” ‘get the Categories collection Set colCategories = g_oNS.Categories ‘see if there’s a “Business” category Set oCategory = colCategories.Item(“Business”) If (oCategory Is Nothing) Then ‘if not, create it Set oCategory = colCategories.Item(“Business”) oCategory.Color = olCategoryColorLightTeal light teal color End If ‘get the Rules collection Set colRules = g_oNS.DefaultStore.GetRules ‘assume here the rule doesn’t already exist. ‘we could iterate the Rules collection to verify that. (continued) 95
Slide 121: Chapter 4: Outlook VBA Listing 4.16 (continued) ‘create new receive rule Set oRule = colRules.Create(sName, olRuleReceive) ‘rule condition: sender email address aryEmail(0) = sEmail Set oRuleCondition = oRule.Conditions.SenderAddress With oRuleCondition .Enabled = True ‘enable the condition .Address = aryEmail ‘this takes an array of email addresses End With ‘rule action: assign to one or more categories aryCategories(0) = “Business” Set oRuleAction = oRule.Actions.AssignToCategory With oRuleAction .Enabled = True ‘enable the action .Categories = aryCategories ‘takes an array of categories End With colRules.Save ‘save the rule ‘to run the rule immediately ‘oRule.Execute End If Set oMail = Nothing Set colRules = Nothing Set oRule = Nothing Set oRuleCondition = Nothing Set oRuleAction = Nothing Set colCategories = Nothing Set oCategory = Nothing End Sub ‘fires when any context menu is closed Private Sub oOL_ContextMenuClose(ByVal ContextMenu As OlContextMenu) Dim oControl As Office.CommandBarControl ‘look for the Item context menu If ContextMenu = olItemContextMenu Then If Not (oBar Is Nothing) Then ‘find our control (menu item) and delete it if it exists Set oControl = oBar.FindControl(msoControlButton, , _ “BusinessCategoryRule”) If Not (oControl Is Nothing) Then oControl.Delete Set oButton = Nothing Set oBar = Nothing End If End If End If Set oControl = Nothing End Sub 96
Slide 122: Chapter 4: Outlook VBA Listing 4.16 (continued) ‘fires when the Item context menu is opened Private Sub oOL_ItemContextMenuDisplay(ByVal _ CommandBar As Office.CommandBar, ByVal Selection As Selection) Dim strCaption As String Dim strTag As String ‘only if 1 mail item is selected If (Selection.Count = 1) And _ (Selection.Item(1).Class = olMail) Then ‘get the GUID for the selected item, ‘store it for the Click event handler m_EntryID = Selection.Item(1).EntryID strCaption = “Categorize all emails from this sender as Business” strTag = “BusinessCategoryRule” ‘tags should be unique ‘create the button (menu item) Set oButton = CommandBar.Controls.Add(msoControlButton, , , , True) With oButton .Caption = strCaption .Tag = strTag .BeginGroup = True ‘add a menu separator End With Set oBar = CommandBar End If End Sub A menu item on any Outlook folder menu is a CommandBarControl. Menus are CommandBar objects and are members of the CommandBars collection. In previous versions of Outlook, the menus for Explorers and Inspectors both follow this model, where the CommandBars collection is exposed as a part of the Office library and CommandBar objects are accessed as properties of the Explorer or Inspector objects. This still applies with Outlook 2007, but in Outlook 2007 Inspector CommandBar objects and controls are relegated to the Add-Ins tab in the new Ribbon interface. The Ribbon can’t be programmed using Outlook VBA; programming the Ribbon requires using a COM addin. Ribbon programming is covered in the chapters on COM addins. The following code lines in Listing 4.16 declare WithEvents a CommandBarButton object that will be your context menu item. This enables the oButton object to handle the Click event that fires when the menu item is clicked. The code also declares a CommandBar object, which is used to hold a reference to the ItemContextMenu. ‘handles Click event Private WithEvents oButton As Office.CommandBarButton Private oBar As Office.CommandBar ‘context menus are CommandBar objects When the context menu is displayed, which happens for the ItemContextMenu when any item is rightclicked in an Outlook folder view (Explorer), the ItemContextMenuDisplay event handler is executed. 97
Slide 123: Chapter 4: Outlook VBA The Selection collection passed in the ItemContextMenuDisplay procedure is checked to see if only one item is selected. If so, the code adds a new menu item, a ComandBarButton, to the context menu: Set oButton = CommandBar.Controls.Add(msoControlButton, , , , True) The code also sets a module-level variable, m_EntryID, to save the GUID of the selected item. The selection is not available in the button click event handler, so it’s saved for future use when the context menu is opened. The custom menu item is shown in Figure 4.8. Figure 4.8 When the menu item is clicked, the oButton_Click event handler procedure is called, which creates a new rule based on the email address of the sender of the selected email. sEmail = oMail.SenderEmailAddress ‘get the sender address ‘name for the new rule we’re creating sName = “Categorize “ & sEmail & “ as Business” The Categories collection of the NameSpace object is checked to see if there’s already a “Business” category, and if not, the Category is created. The Category.Color property is set to light teal. ‘get the Categories collection Set colCategories = g_oNS.Categories ‘see if there’s a “Business” category Set oCategory = colCategories.Item(“Business”) If (oCategory Is Nothing) Then ‘if not, create it Set oCategory = colCategories.Item(“Business”) oCategory.Color = olCategoryColorLightTeal light teal color End If The code then creates a new receive Rule (no checking is done to see if the Rule already exists) with a SenderAddress Condition that the sender email address is the same as the email address of the selected email. The Rule Action is to assign the new Business Category to the email. 98
Slide 124: Chapter 4: Outlook VBA ‘get the Rules collection Set colRules = g_oNS.DefaultStore.GetRules ‘assume here the rule doesn’t already exist. ‘we could iterate the Rules collection to verify that. ‘create new receive rule Set oRule = colRules.Create(sName, olRuleReceive) ‘rule condition: sender email address aryEmail(0) = sEmail Set oRuleCondition = oRule.Conditions.SenderAddress With oRuleCondition .Enabled = True ‘enable the condition .Address = aryEmail ‘this takes an array of email addresses End With ‘rule action: assign to one or more categories aryCategories(0) = “Business” Set oRuleAction = oRule.Actions.AssignToCategory With oRuleAction .Enabled = True ‘enable the action .Categories = aryCategories ‘takes an array of categories End With The SenderAddress property takes an array of email addresses. In this case, only one email address is being tested; a larger array would be used if more than one address were being tested. The AssignToCategory Action also takes an array as its Categories property that allows the rule to assign more than one category to the item if desired. An enhancement to this rule could check to see if the sender is in the user’s contacts, and if so, add all the sender’s email addresses to the SenderAddress property by setting up a larger array for the aryEmail variable. The results of creating this Rule are shown in Figure 4.9, which shows the Categorized Mail search folder with the new Business category in its teal color. Figure 4.9 99
Slide 125: Chapter 4: Outlook VBA Additional Macros The macros in this section of the chapter show additional uses for the Accounts collection, an example of working with the Table object and the hidden All Tasks folder, and how to create search folders from AdvancedSearch objects. Check for Existing Contact The code in Listing 4.17 demonstrates using the Accounts collection and Account objects to determine if an email was sent to someone who is in a contacts list or Exchange Global Address List. Place this code in the Chapter_4 code module, and select an email and run the macro to test this code. Listing 4.17 Public Sub IsExistingContact() Dim oMail As Outlook.MailItem Dim oAE As Outlook.AddressEntry Dim oContact As Outlook.ContactItem Dim oRecip As Outlook.Recipient Dim colAccounts As Outlook.Accounts Dim oAccount As Outlook.Account Dim oEXUser As Outlook.ExchangeUser Dim blnExchange As Boolean ‘get a selected mail item, no error checking for item type Set oMail = Application.ActiveExplorer.Selection.Item(1) ‘get first recipient Set oRecip = oMail.Recipients.Item(1) ‘get AddressEntry object from Recipient Set oAE = oRecip.AddressEntry ‘get contact from AddressEntry ‘this will not return an entry in the GAL or a DL Set oContact = oAE.GetContact If oContact Is Nothing Then ‘see if it’s an entry in the GAL Set colAccounts = g_oNS.Accounts For Each oAccount In colAccounts If oAccount.AccountType = olExchange Then ‘see if Exchange is being used blnExchange = True Exit For End If Next If blnExchange Then ‘if Exchange used Set oEXUser = oAE.GetExchangeUser ‘get the GAL entry If Not (oEXUser Is Nothing) Then ‘if the GAL entry exists show it oEXUser.Details Else ‘not a contact or GAL entry MsgBox “Not a contact or GAL entry”, vbOKOnly + vbInformation End If 100
Slide 126: Chapter 4: Outlook VBA Listing 4.17 (continued) Else ‘not a contact or GAL entry MsgBox “Not a contact or GAL entry”, vbOKOnly + vbInformation End If Else oContact.Display ‘show the contact item End If Set oMail = Nothing Set oAE = Nothing Set oContact = Nothing Set oRecip = Nothing End Sub The code first sets a MailItem object from the first item in the Selection collection of the ActiveExplorer. This is the Explorer that is currently displaying an Outlook folder. If more than one item is selected, only the first item in the Selection collection is used for this code. No error checking is performed to make sure the item is an email item. Set oMail = Application.ActiveExplorer.Selection.Item(1) ‘get first recipient Set oRecip = oMail.Recipients.Item(1) The code then gets the first Recipient of the email from the Recipients collection. Each Recipient object has an AddressEntry property that represents an entry in an Outlook AddressList. Outlook AddressLists can be Contacts folders, the Exchange Global Address List if you are using Exchange server, and AddressLists returned using LDAP queries. The new AddressEntry.GetContact method is used to return a ContactItem object from the AddressEntry: ‘get first recipient Set oRecip = oMail.Recipients.Item(1) ‘get AddressEntry object from Recipient Set oAE = oRecip.AddressEntry ‘get contact from AddressEntry ‘this will not return an entry in the GAL or a DL Set oContact = oAE.GetContact If the AddressEntry isn’t in a Contacts list, the oContact object is Nothing and the code then checks to see if the user is using Exchange server. The Accounts collection is iterated and each Account is examined to see if the AccountType is olExchange, indicating that an Exchange account is present. Set colAccounts = g_oNS.Accounts For Each oAccount In colAccounts If oAccount.AccountType = olExchange Then ‘see if Exchange is being used blnExchange = True Exit For End If Next 101
Slide 127: Chapter 4: Outlook VBA If Exchange was found as an AccountType, an ExchangeUser object is instantiated from the AddressEntry .ExchangeUser property. The Details method of the ExchangeUser property is called to display the Global Address List properties of the ExchangeUser represented by the recipient of the email. Set oEXUser = oAE.GetExchangeUser ‘get the GAL entry If Not (oEXUser Is Nothing) Then ‘if the GAL entry exists show it oEXUser.Details If a ContactItem was found as the recipient of the email, the contact is displayed. Check Today’s To-Do List The GetTodaysToDoList code scans the hidden All Tasks folder that’s used to populate the To-Do Bar and lists all tasks due today in the ToDoForm, you created earlier in this chapter. This macro uses the new Table object exposed by the hidden All Tasks folder to quickly scan the folder and return only tasks due today. Place the code for Listing 4.18 in the Chapter_4 code module and run it to display today’s to-do list. Listing 4.18 Public Sub GetTodaysToDoList() Dim oStore As Outlook.Store Dim oFolder As Outlook.Folder Dim oTable As Outlook.Table Dim FilteredTable As Outlook.Table Dim colColumns As Outlook.Columns Dim oColumn As Outlook.Column Dim oRow As Outlook.Row Dim strFilter As String Dim aryValues() As Variant Dim i As Long ‘DueDate strFilter = “http://schemas.microsoft.com/mapi/id/“ strFilter = strFilter & _ “{00062003-0000-0000-C000-000000000046}/81050040” ‘using the “@SQL=” syntax for the table filter strFilter = “@SQL=” & Chr(34) & strFilter & Chr(34) _ & “ = ‘“ & Format(Date, “General Date”) & “‘“ Set oStore = g_oNS.DefaultStore ‘get the hidden folder used for the ToDo Bar Set oFolder = oStore.GetSpecialFolder(olSpecialFolderAllTasks) Set oTable = oFolder.GetTable ‘Table for the ToDo Bar folder Set FilteredTable = oTable.Restrict(strFilter) ‘filter for due today 102
Slide 128: Chapter 4: Outlook VBA Listing 4.18 (continued) If FilteredTable.EndOfTable Then MsgBox “Nothing to do today, a good day for fishing!”, _ vbInformation + vbOKOnly Else i=0 Set colColumns = FilteredTable.Columns With colColumns ‘remove default columns and add our own. ‘EntryID and Subject would be there as default columns .RemoveAll Set oColumn = .Add(“Subject”) Set oColumn = .Add(“EntryID”) ‘if this was not a named property we would have to convert ‘ from UTC to local time using the Row.UTCToLocalTime method Set oColumn = .Add(“DueDate”) End With ToDoForm.lstToDo.Clear ‘clear the ToDo list ListBox control Do While Not (FilteredTable.EndOfTable) Set oRow = FilteredTable.GetNextRow ‘get a row aryValues = oRow.GetValues ‘get the 3 values we asked for With ToDoForm.lstToDo ‘add a new row to the list and add 3 columns of data .AddItem .Column(0, i) = aryValues(0) ‘Subject .Column(1, i) = aryValues(2) ‘DueDate .Column(2, i) = aryValues(1) ‘EntryID End With i=i+1 Loop ToDoForm.Show vbModal End If Set oStore = Nothing Set oFolder = Nothing Set oTable = Nothing Set FilteredTable = Nothing Set colColumns = Nothing Set oColumn = Nothing Set oRow = Nothing End Sub The filter used to return only tasks due today is constructed using the property tag “http://schemas .microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/81050040”, which is the 103
Slide 129: Chapter 4: Outlook VBA DueDate of a task. The filter uses the “@SQL=” syntax to compare the DueDate property for each item in the Table for the folder with the date today. strFilter = “@SQL=” & Chr(34) & strFilter & Chr(34) _ & “ = ‘“ & Format(Date, “General Date”) & “‘“ A Store object is instantiated from the NameSpace.DefaultStore and the hidden All Tasks folder is instantiated as a Folder object using the GetSpecialFolder method of the Store object. This method enables you to get references to the All Tasks folder and the Reminders folder. Both folders are special search folders that are hidden in the Outlook user interface. The Folder’s Table is returned by the GetTable method used to get the Table for any folder. Set oStore = g_oNS.DefaultStore ‘get the hidden folder used for the ToDo Bar Set oFolder = oStore.GetSpecialFolder(olSpecialFolderAllTasks) Set oTable = oFolder.GetTable ‘Table for the ToDo Bar folder Set FilteredTable = oTable.Restrict(strFilter) ‘filter for due today After the filter is applied as a Table.Restrict clause, the only available rows in the Table will be rows that match the filter condition. The Table.Columns collection normally contains default columns representing commonly used properties such as EntryID. In this case, the default columns are cleared using the RemoveAll method and three new columns are added to the Table for Subject, EntryID, and DueDate. If DueDate were not a named property the date would have to be converted from UTC time into local time using the Row.UTCToLocalTime method. A corresponding Row.LocalTimeToUTC method is available to convert UTC time to local time for properties that don’t get automatically converted by Outlook into local time. Under the hood, all times are stored by Outlook and MAPI as UTC time values. Set colColumns = FilteredTable.Columns With colColumns ‘remove default columns and add our own. ‘EntryID and Subject would be there as default columns .RemoveAll Set oColumn = .Add(“Subject”) Set oColumn = .Add(“EntryID”) ‘if this was not a named property we would have to convert ‘ from UTC to local time using the Row.UTCToLocalTime method Set oColumn = .Add(“DueDate”) End With After the table filter and columns are set up, the code checks to make sure that something was returned by evaluating the Table.EndOfTable property. If that property is false the code reads each row in turn using the GetNextRow method into a Row variable. The columns in the row are put into a multi-column listbox control that displays the Subject and DueDate and hides the EntryID property. If a Row is double-clicked, the code opens that item for review. Do While Not (FilteredTable.EndOfTable) Set oRow = FilteredTable.GetNextRow ‘get a row 104
Slide 130: Chapter 4: Outlook VBA aryValues = oRow.GetValues ‘get the 3 values we asked for With ToDoForm.lstToDo ‘add a new row to the list and add 3 columns of data .AddItem .Column(0, i) = aryValues(0) ‘Subject .Column(1, i) = aryValues(2) ‘DueDate .Column(2, i) = aryValues(1) ‘EntryID Incomplete Tasks Search Folder Place the code in Listing 4.19 in the Chapter_4 code module and run it as a macro to create a search folder that aggregates all items marked as tasks that aren’t marked as complete. This code is very similar to the code in Listing 4.8; the difference is this code saves the completed search as a search folder, visible and usable in the user interface. Listing 4.19 Sub CreateIncompleteTasksSearchFolder() Dim objSch As Outlook.Search Dim strFilter As String Dim strScope As String Dim strTag As String Dim Completed As String ‘task complete Completed = “http://schemas.microsoft.com/mapi/id/“ Completed = Completed & _ “{00062003-0000-0000-C000-000000000046}/811C000B” strFilter = Completed & “ = ‘False’“ ‘Scope is entire store, either Personal Folders or Exchange mailbox. ‘Uncomment the following lines as appropriate for the store type strScope = “‘//Personal Folders’“ ‘strScope = “‘//Mailbox - Casey Slovak’“ strTag = “notcompleted” Set objSch = Application.AdvancedSearch(Scope:=strScope, _ Filter:=strFilter, SearchSubFolders:=True, Tag:=strTag) If objSch Is Nothing Then MsgBox “Sorry, the search folder could not be created.” End If ‘Saving this search creates a new search folder objSch.Save “Uncompleted Tasks” Set objSch = Nothing End Sub 105
Slide 131: Chapter 4: Outlook VBA The property tag “http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000000000000046}/811C000B” is used for the Complete property. This is done because the Complete property isn’t exposed in the Outlook object model for items that aren’t TaskItems. Make sure to change the scope string in Listing 4.19 to the name of your Exchange mailbox or PST file as shown in the Outlook user interface for this code to work. To create a search folder from a completed AdvancedSearch, save the search with the name to use for the search folder: objSch.Save “Uncompleted Tasks” Overdue Tasks Search Folder Place the code in Listing 4.20 in the Chapter_4 code module, and run it as a macro to create a search folder that aggregates all overdue tasks in the default Tasks folder and its subfolders into a search folder named Overdue Tasks. Listing 4.20 Sub CreateOverdueTasksSearchFolder() Dim objSch As Outlook.Search Dim strFilter As String Dim strScope As String Dim strTag As String ‘task due date Const DueDate As String = _ “http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000-000000000046}/81050040” ‘Create a folder that shows all tasks due and overdue strFilter = DueDate & “ <= ‘today’“ ‘Scope is Tasks folder and any subfolders strScope = “Tasks” strTag = “RecurSearch” Set objSch = Application.AdvancedSearch(Scope:=strScope, Filter:=strFilter, _ SearchSubFolders:=True, Tag:=strTag) If objSch Is Nothing Then MsgBox “Sorry, the search folder could not be created.” End If ‘Saving this search creates a new search folder objSch.Save “Overdue Tasks” Set objsearch = Nothing End Sub The property tag “http://schemas.microsoft.com/mapi/id/{00062003-0000-0000-C000000000000046}/81050040” is used for the DueDate property. 106
Slide 132: Chapter 4: Outlook VBA The TaskItem.DueDate property doesn’t have a time component. Any search filter or restriction using DueDate should always strip out the time component of the filter data to ensure that the filter works as intended. Importance Search Folder Place the code in Listing 4.21 in the Chapter_4 code module, and run it as a macro to create a search folder named High that finds all items marked as High Importance in the Inbox and Sent Items folders and their subfolders. Listing 4.21 Sub CreateImportanceSearchFolder() Dim objSch As Outlook.Search Dim strFilter As String Dim strScope As String Dim strTag As String ‘item Importance strFilter = “urn:schemas:httpmail:importance” strFilter = strFilter & “ = ‘2’“ ‘ OlImportanceHigh ‘Scope is Inbox and Sent Items folders and subfolders of those folders strScope = “‘Inbox’, ‘Sent Items’“ strTag = “High” Set objSch = Application.AdvancedSearch(Scope:=strScope, Filter:=strFilter, _ SearchSubFolders:=True, Tag:=strTag) If objSch Is Nothing Then MsgBox “Sorry, the search folder could not be created.” End If ‘Saving this search creates a new search folder objSch.Save “High” ‘Change search folder name to what you want Set objSch = Nothing End Sub The property tag “urn:schemas:httpmail:importance” is used for the Importance property. Most Outlook items have an Importance property. Changing the search scope to the name of your Exchange mailbox or PST file populates the search folder with Outlook items of every type that have Importance set to OlImportanceHigh. Overdue Reminders Search Folder Place the code in Listing 4.22 in the Chapter_4 code module, and run it as a macro to create a search folder named Overdue that finds all items with overdue reminders. 107
Slide 133: Chapter 4: Outlook VBA Listing 4.22 Sub CreateOverdueRemindersSearchFolder() Dim objSch As Outlook.Search Dim strFilter As String Dim strScope As String Dim strTag As String ‘reminder time Const ReminderTime As String = “urn:schemas:calendar:remindernexttime” strFilter = ReminderTime & “ < ‘today’“ ‘Scope is entire store, either Personal Folders or Exchange mailbox. ‘Uncomment the following lines as appropriate for the store type strScope = “‘//Personal Folders’“ ‘strScope = “‘//Mailbox - Casey Slovak’“ strTag = “overduereminders” Set cSearch = New clsSearch cSearch.Tag = strTag Set objSch = Application.AdvancedSearch(Scope:=strScope, Filter:=strFilter, _ SearchSubFolders:=True, Tag:=strTag) ‘Saving this search creates a new search folder objSch.Save “Overdue” ‘Change search folder name to what you want Set objSch = Nothing End Sub The property tag “urn:schemas:calendar:remindernexttime” is used for the ReminderSet property. The code finds items of every Outlook type that supports reminders. Make sure to change the scope string in Listing 4.22 to the name of your Exchange mailbox or PST file as shown in the Outlook user interface for this code to work. Running and Distributing Macros In this section, you learn the various ways that Outlook macros can be run, and how to distribute Outlook macros and the VBA project. Running Macros There are three ways to manually run a macro in the Outlook VBA project: 1. Select the macro in the Macros dialog, as shown in Figure 4.1, and click the Run button in the Macros dialog. 108
Slide 134: Chapter 4: Outlook VBA 2. 3. Open the Outlook VBA project, find the macro in your VBA code, place your cursor in the macro procedure, and click the Run icon or press F5. Put the macro on an Outlook toolbar or menu line and use the button to run the macro. To put the macro on a button in a toolbar or menu line, right-click a toolbar or menu line and select Customize. In the Customize dialog, select the Commands tab and select Macros in the left-hand list. Scroll the right-hand list down to find your macro, and use the mouse to drag it to the toolbar or menu line where you want the button to appear. Figure 4.10 shows a macro placed on the Menu Bar line, to the right of the Help menu. While in Customize mode you can right-click the new macro button, select Name, and rename the button to something meaningful. Figure 4.10 In general, it’s a good rule to only add macros you run frequently to the existing toolbars or menu line. Distributing the VBA Project Distributing a VBA project to other people is an exercise in overwriting any existing macros the people may have. You have to send the other people your VBAProject.OTM file, and they have to replace any existing VBA file they have with the distributed VBAProject.OTM file while Outlook isn’t running. This automatically wipes out any existing macros the people have in their old VBA project. The usual path to the OTM file is hidden by default in Windows and may not be available even if hidden files and folders are set to be visible in Windows Explorer because of user permissions. VBA projects are intended to be personal code, used for macros or prototyping, not for distribution. A far better solution is to prototype your code in VBA, then write a COM addin that encapsulates your functionality and distribute the COM addin to other people. This also has the advantage of avoiding problems 109
Slide 135: Chapter 4: Outlook VBA with macro security settings and signing macro code. The certificate generated by the Selfcert utility is not traceable back to a verified signing authority, so usually that certificate cannot be trusted on a different computer than the one you used to generate the certificate. Distributing Individual Macros You can distribute individual macros by putting the macro code in a code module and exporting the code module, using the File menu’s Export function. The resulting exported code module is a file with an extension of .BAS, which can be opened in Outlook VBA using the File, Import menu function. The code module file can also be opened directly in VB 6. This is certainly not an automated process; the sender has to export the macro code and transfer it to the recipient, and the recipient has to import the .BAS file into his or her VBA project, but at least the process can be done and it doesn’t overwrite any existing macros the user might have. Summar y In this chapter, you learned more about Outlook VBA code and creating and running macros in Outlook. Macros in this chapter work with: ❑ ❑ ❑ ❑ ❑ ❑ The Rules collection Application-wide events UserForms Classes and user-created events Wrapper classes Context menus In the next chapter, you will learn about customizing Outlook forms and how to add form regions to forms. 110
Slide 136: Outlook Forms The forms Outlook uses to show open items can be customized by adding controls and code. You often use custom form applications for things such as Help Desk tickets, routing of approval or review forms, and human resources forms. These custom forms and form applications were the only way of providing a custom user interface for Outlook items until Outlook 2007. Form regions are a new way to customize an item’s user interface and provide opportunities for customization that aren’t available with standard custom forms. No investment was made in new features for custom forms in Outlook 2007, and none has been made for many versions of Outlook. Microsoft is encouraging forms development for Outlook 2007 to concentrate on using form regions. Of course, this approach works only if every user is running Outlook 2007 and won’t work if the forms are used with earlier versions of Outlook. In this chapter, you learn how to customize Outlook forms, and the advantages and disadvantages of using custom forms. You also learn about form regions and how to design and use them. A complete discussion of Outlook custom forms is a big topic that can take up an entire book, so only the basics of custom forms are covered in this chapter. For more information about designing and working with custom forms and lots of information about various problems and solutions for custom forms, I recommend looking at the material at www.outlookcode.com/d/forms.htm. Wor king with Standard Forms All Outlook items have a MessageClass property that determines what type of object the item is: contact, appointment, task, email, post, journal entry, or variations of those basic types. Note items aren’t included. They can’t be customized and, more often than not, attempting to work with them in code causes the item or Outlook to crash.
Slide 137: Chapter 5: Outlook Forms All Outlook forms have a MessageClass that starts with “IPM,” which stands for Inter-Personal Message. The standard Outlook form types are all derived from that basic building block, such as IPM.Appointment for appointment forms and IPM.Activity for journal entry forms. When you customize an Outlook form it’s published as a derived MessageClass, such as IPM.Contact.MyContactForm. The only way to create a completely new MessageClass derived from the “IPM” base class is to use Extended MAPI and C++ or Delphi. You cannot create a new MessageClass with the Outlook object model. Forms Libraries Standard forms are stored in the Standard Forms Library. To choose a form for customization, select Tools ➪ Forms ➪ Design a Form and choose a form from the Standard Forms Library in the Look In drop-down, as shown in Figure 5-1. Figure 5-1 To open an Outlook template stored in the file system as an OFT (Outlook form template) file, select User Templates in the File System in the Look In drop-down; then click the Browse button to navigate to where the OFT file is stored. The previous alternate method of opening OFT files by double-clicking them in Windows Explorer does not work with Outlook 2007 in most cases. This change was made by Microsoft for security reasons. Form code won’t run in OFT files, also for security reason. Form code only runs in published forms. The only OFT files that can be opened from the file system are those with no custom controls and no code, a very small subset of all custom forms. OFT files are a good way to distribute forms that will be published using code. OFT files are also a good way to transfer custom forms so they can be designed on different computers. 112
Slide 138: Chapter 5: Outlook Forms Published Forms Publishing a form saves the form in one of the Outlook forms repositories with the custom MessageClass you supply. It also enables the form to run code and makes the form available to anyone who has access to the location where the form is published. The following forms repositories are available by default: ❑ ❑ ❑ ❑ ❑ ❑ ❑ Standard Forms Library Organizational Forms Library Personal Forms Library Outlook Folders Standard Templates User Templates in File System List of Outlook Folders The Organizational Forms Library is only available if you are using Exchange server. To open previously published forms for design, select the Personal Forms Library (or other location where the form was published) in the Look In drop-down of the Design Form dialog shown in Figure 5-1. Form Customization The following table lists the standard forms that are available for customization in the Standard Forms Library, their MessageClass and whether you can customize their default pages. Form Appointment Contact Journal Entry Meeting Request (Hidden) Message Post RSS Article (Hidden) Task Task Request (Hidden) MessageClass IPM.Appointment IPM.Contact IPM.Activity IPM.Schedule.Meeting.Request IPM.Note IPM.Post IPM.Post.Rss IPM.Task IPM.TaskRequest Customize Default Pages No General tab only No No Yes Yes No No No 113
Slide 139: Chapter 5: Outlook Forms Forms have five pages available for customization, named P.2 to P.6. Some form types also allow you to customize one or more of the default pages, as indicated by the previous table. A form page that is available for customization is a design surface, a place to put controls for data input or display. The standard controls used on a form belong to the MS Forms 2.0 library, listed as MSForms in the Outlook VBA project references. You can also place Active X controls on a form page, although many Active X controls do not run correctly when placed on a form. The only way to tell if a specific Active X control will work on a form is to try it. .NET Windows Forms controls can’t be used as controls on a form page. All customization for a form is stored in the FormDesign property for that MessageClass, a binary property that contains any controls, customization instructions and settings, and form code. Form code is only written using VBScript, no other language can be used, including any of the other scripting languages such as JavaScript. In previous versions of Outlook, you could place an open form into design mode by selecting Tools ➪ Forms ➪ Design This Form. To display this option in the Outlook 2007 Ribbon, the Developer tab must be enabled. To enable the Developer tab select the Office menu in an open item and click the Editor Options button. Check the Show Developer tab in the Ribbon to enable the tab in all Office applications. Custom forms don’t show the new controls added to Outlook forms, such as the new time zone controls in appointments or the picture control that was added to Outlook contact forms in Outlook 2003. Custom forms also do not use any Windows theming, making custom form pages look different from standard forms or form pages. What you see on a form page that can be customized is what that form type looked like in Outlook 2002, with none of the later form features added to the form. Advantages and Disadvantages of Custom Forms Custom forms are the only way to customize the form user interface other than form regions with Outlook 2007. However, they are old technology that hasn’t been updated very much since the early days of Outlook, and custom forms aren’t the most robust solution possible, being prone to various forms of corruption. Advantages of Custom Forms The following list shows some of the advantages of using custom forms for Outlook solutions: ❑ Custom forms are the only way to customize the user interface for Outlook items that’s compatible with all versions of Outlook. Form regions are only usable for Outlook 2007, and InfoPath forms are only usable for Outlook 2003 and 2007. Custom forms can use Active X controls to display data from other applications, such as data retrieved from a database and displayed in a grid control in the Outlook form. Form customizations integrate with standard forms. Other technologies, such as forms displayed from menu or Ribbon commands are separate forms and are not integrated into the item user interface. Individual custom forms are very small in size, the bulk of the form design is stored in the forms cache and the published form, so complex forms can take up very little space in an Outlook store. ❑ ❑ ❑ 114
Slide 140: Chapter 5: Outlook Forms ❑ ❑ Custom forms can be made the default forms for folders, automatically opening or being used in those folders for all items. Custom forms can be set to open as the standard form for a default MessageClass using a registry setting, allowing you to substitute a custom form for all standard forms of the replaced MessageClass. An example is a custom contact form that is set to be the default contact form used by an organization in Outlook. Disadvantages of Custom Forms The following list shows some of the disadvantages of using custom forms for Outlook solutions: ❑ When you first open a custom form, the form design is stored in the forms cache. Whenever you open a new form of that MessageClass, the form design is subsequently retrieved from the forms cache. This saves bandwidth and time for forms published on a server, such as forms published in the Organizational Forms Library. However, the forms cache is prone to corruption when a previous version of a form is used instead of the latest version, or the default form for the base class of the custom MessageClass is used instead of the custom MessagClass. Custom forms can become one-offed, with the form design embedded in the form instead of coming from the forms cache or forms library. One-offed forms cannot run code, and because the form design is embedded in the item, are much larger than forms where the form design is not embedded in the item. One-offed forms don’t use any revisions to the form; they always use the embedded form design. You can only view custom forms in Outlook, and sending them over the Internet can be problematical. The forms description for the custom form is sent using Rich Text, encapsulated in what Microsoft calls Transport Neutral Encapsulation Format (TNEF). If a custom form is sent using plain text or HTML, the form description is not transmitted and the form arrives as a standard item of that base type. Sending using Rich Text is dependent on Outlook global settings, settings on the Exchange server if one is used, and individual settings for sending to that contact. Many things can go wrong to prevent a form from being transmitted correctly over the Internet. When the form isn’t transmitted correctly, the recipient receives a standard form, which may or may not be accompanied by an attached Winmail.dat file that holds the TNEF component of the form. The Winmail.dat attachment is usually hidden and used to reconstruct the custom form. Custom forms sent to others won’t display correctly, and any code they contain won’t run, unless the form is published and the published form is accessible to the recipient. Care must be taken when publishing custom forms to always mark changes using the Version property; otherwise, the forms cache will become corrupted, and old versions of the form will be used. Care must also be taken to ensure that a form is published to only one forms repository. Publishing a form to multiple places is another recipe for forms cache corruption. Forms can become unstable if you add too many controls to a custom form. How many controls is too many depends on what version of Outlook you use to open a form, and whether or not you’re using Exchange. A good rule of thumb is to use fewer than 100 controls on a custom form. Custom forms created in one version of Outlook may disappear from memory or even crash Outlook when opened in a different version of Outlook. ❑ ❑ ❑ ❑ ❑ 115
Slide 141: Chapter 5: Outlook Forms Back to the Past: the Forms Development Environment If you’re used to developing code and forms in VBA, VB 6, or any of the .NET platforms, you are likely to be dismayed by the primitive development environment for custom forms development: ❑ ❑ ❑ ❑ ❑ The code editor is a slightly modified version of Notepad. There is no Intellisense or other code helpers in the forms development environment. Limited subsets of the events available for an Outlook item are available as templates in the event handler. The forms script debugger is the Microsoft Script Debugger, which has limited functionality when used with forms code. Form code uses VBScript, so all variables are untyped Variants. Because of these limitations, it often makes sense to prototype form code using the VBA environment, which has a much better code development environment. When you open a custom form for design, it opens to the default page, the same page displayed when a standard form is opened. This page may or may not be customizable, see the table in the “Form Customization” section for a list of forms that permit you to customize the default pages. You can easily recognize a customizable page by the grid displayed in the form background and controls that don’t use Windows theming. Figure 5-2 shows the P.2 customizable page for a task form, with the forms design Ribbon displayed in the Developer tab of the form. Figure 5-2 116
Slide 142: Chapter 5: Outlook Forms The following table shows the functions performed by the menu items in the Developer tab of the Ribbon. Ribbon Group Code Ribbon Menu Item Visual Basic View Macros Script Debugger Menu Function Opens the Outlook VBA project Display the Macros selection dialog Opens the script debugger window in a running form that has code Opens the Macro Security page of the Trust Center Form publication choices Opens the form code window Runs the form being designed Selections to display and rename the form page Selections to create, open, save, and close form regions Checkbox to enable or disable separate read and compose layouts for the form Edits the compose page when separate layouts are enabled Edit the read page when separate layouts are enabled Opens the Field Chooser window Opens the Control Toolbox window Opens the Properties window for the selected page or control Opens the Advanced Properties window for the selected page or control Continued Macro Security Form Publish View Code Run This Form Design Page Form Region Separate Read Layout Edit Compose Page Edit Read Page Tools Field Chooser Control Toolbox Property Sheet Advanced Properties 117
Slide 143: Chapter 5: Outlook Forms Ribbon Group Arrange Ribbon Menu Item Region Layout Bring to Front Send to Back Tab Order Menu Function Layout and sizing for form region controls Brings the selected controls to the front of the Z-order Sends the selected controls to the back of the Z-order Opens the Tab Order dialog for setting the tab order of the controls on a form Selections for aligning the selected controls in various formats Groups the selected controls into a control group Selections for sizing selected controls Align Group Size The About This Form Help menu command is not available in the Ribbon. This command displays information that can be useful when debugging custom forms such as version, file number, contact, and description text. You can add this command to the QAT to make it available when running a custom form, but because each Ribbon is different, the command must be added to each form where you want it to be available. The following sections show how to use the controls in the Developer tab of the Ribbon to design a custom form. Using the Field Chooser Form pages are only design surfaces. The controls on a page don’t retain values unless the controls are bound to an Outlook property. One of the most common mistakes that a new Outlook forms programmer makes is to expect controls on Outlook custom forms to retain data when the controls aren’t bound to Outlook properties. Outlook properties store the data for a form and the controls on the form display that data when the form is loaded, if the controls are bound to Outlook properties. If the controls are not bound to Outlook properties, the only way to populate the controls with stored data is to use code. Controls can be bound to built-in or custom Outlook properties. The Field Chooser provides an easy way to bind controls to properties, both built in and custom. The Field Chooser is also used to create new properties. Figure 5-3 shows an Appointment form in design mode, with the P.2 page selected. Open the Field Chooser by clicking the Field Chooser item in the Tools section of the Developer Ribbon tab. Make sure Frequently used fields is selected in the drop-down control at the top of the Field Chooser, and drag the All Day Event item from the Field Chooser to the form. This automatically creates a CheckBox control on the form page and binds that CheckBox control to the AllDayEvent Outlook property. 118
Slide 144: Chapter 5: Outlook Forms Figure 5-3 Many properties in the Field Chooser use the same controls used by Outlook for the properties, however some Outlook controls aren’t exposed through the Field Chooser. Dragging the End property to the form creates a label control and a textbox with the End date and time, not the standard Outlook End time and date picker controls. Clicking the New button at the bottom of the Field Chooser enables you to create a new Outlook property that can be bound to a suitable control added to a form. The new property is shown in the Field Chooser when the user-defined fields in the folder selection are made in the drop-down control at the top of the Field Chooser. Other selections in the drop-down control enable you to select all controls exposed for a type of item, such as appointment or contact, and to select fields previously created in the folder or in custom Outlook forms. Using the Control Toolbox You open the Control Toolbox by clicking the tools icon in the Ribbon, which by default displays only some of the controls from the MS Forms 2.0 controls library. If a control you want to use is not shown in the Control Toolbox, right-click on the Control Toolbox and select Custom Controls to open a list of all Active X controls registered on that development computer. All Microsoft Forms 2.0 controls can be added to the Toolbox and used in forms. Many other registered Active X controls can also be used, with limitations on the properties and events that are usable in a form. Figure 5-4 shows the Control Toolbox with the Forms 2.0 controls and the Outlook View Control, Body control, and the Recipient control. These controls are the only controls guaranteed to be compatible with custom forms. It’s tempting to use the new themed Outlook controls such as the date and time pickers in your forms, but only do so after testing the control in your form application to make sure that it works correctly. The controls may work most of the time but aren’t tested or guaranteed for use in custom forms, so if you use any of these controls, test them each time you use them. 119
Slide 145: Chapter 5: Outlook Forms Figure 5-4 Active X controls used on an Outlook form usually expose only a Click or Change event. Most other control events won’t fire when used on a custom form. Many properties exposed by Active X controls aren’t available in custom forms. If they are used they will return errors or crash the form. There’s no general rule as to which control properties will be available in a form; that’s unique to a control and must be determined empirically by testing. If you use an Active X control that requires licensing on a custom form, you must also distribute the license for the control with your custom form. This usually requires registering the control and providing a license key for it to be installed on the user’s computer, although details of distributing licensed controls vary with the control vendor. Using the Code Window The code window is where VBScript code for the form is stored. The code editor is a simple Notepadlike editor that’s very limited and doesn’t provide a modern code development environment. To open the code window, click the View Code item in the Form group of the Developer tab. To add a prototype of an event handler to the form’s code window, select Script ➪ Event Handler in the code window, as shown in Figure 5-5. Figure 5-5 120
Slide 146: Chapter 5: Outlook Forms The list of prototype event handlers in the code window doesn’t include any events added after Outlook 2002. To handle any newer events, you must manually enter the event handler function prototype in the code window. The following table shows the available Outlook item events and the prototypes for those events in VBA and form code. VBA Form Code In Event Handler Yes Sub Item_AttachmentAdd(ByVal Attachment As Attachment) Sub Item_AttachmentRead(ByVal Attachment As Attachment) Sub Item_AttachmentRemove(ByVal Attachment As Attachment) Sub Item_BeforeAttachmentAdd(ByVal Attachment As Attachment, Cancel As Boolean) Sub Item_BeforeAttachmentPreview(ByVal Attachment As Attachment, Cancel As Boolean) Sub Item_BeforeAttachmentRead(ByVal Attachment As Attachment, Cancel As Boolean) Sub Item_BeforeAttachmentSave(ByVal Attachment As Attachment, Cancel As Boolean) Sub Item_BeforeAttachmentWriteToTempFile(ByVal Attachment As Attachment, Cancel As Boolean) Sub Item_BeforeAutoSave(Cancel As Boolean) Sub Item_BeforeCheckNames(Cancel As Boolean) Sub Item_BeforeDelete(ByVal Item As Object, Cancel As Boolean) Sub Item_AttachmentAdd(ByVal NewAttachment) Sub Item_AttachmentRead(ByVal ReadAttachent) Sub Item_AttachmentRemove(ByVal RemoveAttachment) Function Item_BeforeAttachmentAdd(ByVal AddAttachment) Function Item_BeforeAttachmentPreview(ByVal PreviewAttachment) Function Item_BeforeAttachmentRead(ByVal ReadAttachment) Function Item_BeforeAttachmentSave(ByVal SaveAttachment) Function Item_ BeforeAttachmentWriteToTempFile(By Val WriteAttachment) Yes No No No No Yes No Function Item_BeforeAutoSave() No Yes Yes Function Item_BeforeCheckNames() Function Item_BeforeDelete(ByVal Item) Continued 121
Slide 147: Chapter 5: Outlook Forms VBA Form Code In Event Handler Yes Yes Sub Item_Close(Cancel As Boolean) Sub Item_CustomAction(ByVal Action As Object, ByVal Response As Object, Cancel As Boolean) Sub Item_CustomPropertyChange(ByVal Name As String) Function Item_Close() Function Item_CustomAction(ByVal Action, ByVal NewItem) Sub Item_CustomPropertyChange(ByVal Name) Yes Sub Item_Forward(ByVal Forward As Object, Cancel As Boolean) Sub Item_Open(Cancel As Boolean) Sub Item_PropertyChange(ByVal Name As String) Sub Item_Read() Sub Item_Reply(ByVal Response As Object, Cancel As Boolean) Sub Item_ReplyAll(ByVal Response As Object, Cancel As Boolean) Function Item_Forward(ByVal ForwardItem) Function Item_Open() Sub Item_PropertyChange(ByVal Name) Function Item_Read() Function Item_Reply(ByVal Response) Function Item_ReplyAll(ByVal Response) Yes Yes Yes Yes Yes Yes Sub Item_Send(Cancel As Boolean) Sub Item_Unload() Sub Item_Write(Cancel As Boolean) Function Item_Send() Sub Item_Unload() Function Item_Write() Yes No Yes Most of the form code events are declared as Functions, only the AttachmentAdd, AttachmentRead, AttachmentRemove, CustomPropertyChange, PropertyChange and Unload events are declared as Subs. Events that provide a Cancel argument used to cancel the event don’t provide that argument in form code event handlers. To cancel the event, set the event Function to False in the event handler code. Prototyping Forms Code in VBA I strongly recommend using the VBA environment as a prototyping environment for all but the most trivial form code. This makes code development and debugging much easier than developing from scratch 122
Slide 148: Chapter 5: Outlook Forms in the forms design environment and makes available the enhanced editor, Intellisense, code formatting, Object Browser, Help, and other facilities of the VBA design environment. Simulating the Form Environment in VBA The following steps are used to simulate the code environment for a form in the VBA environment: 1. 2. 3. 4. Create the form, and add the desired controls to the form. Run the form so that it’s available to the VBA environment. Use the Run This Form item in the Form group of the Developer tab to run the form. Set focus to the main Outlook Explorer window, and use the Alt+F11 keyboard shortcut to open the Outlook VBA window. Add a class module to the VBA project to contain the code and event handlers for the form. Select Insert ➪ Class Module to insert the class and give it a descriptive name associated with the form so that you know later what the class is used for. Code modules cannot handle events, so the event handler code must be placed in a class module. In this case, for a custom appointment form, use the name AppointmentCode for the class module. You can use the ThisOutlookSession class module to contain your event-handling code, but that class will quickly become packed with code that is specific to certain forms, so it’s better to use a different class for each form to which you want to add code. 5. 6. Add a class level declaration for Item that’s set to the form item when the code class is initialized. This Item declaration should be declared WithEvents so that the code in the class can handle various AppointmentItem events. Select Class in the Object drop-down control at the top left of the class and in the right-hand Procedure drop-down control select Initialize to add a class initialization procedure to the code. The code in the class should look like this when the initial setup is complete: ‘ Change this item type based on the custom form type Private WithEvents Item As Outlook.AppointmentItem Private Sub Class_Initialize() Set Item = Application.ActiveInspector.CurrentItem End Sub To create an instance of the class and instantiate it, create the following object declaration and macro in a code module or ThisOutlookSession class module: Public AppointmentTestCode As AppointmentCode Sub RunAppointmentCode() Set AppointmentTestCode = New AppointmentCode End Sub 123
Slide 149: Chapter 5: Outlook Forms When the macro is executed, and a new instance of the class is instantiated the Class_Initialize procedure in the class module runs automatically. This sets the Item object in the class to the item displayed in the ActiveInspector object. The ActiveInspector object should be the custom form that was placed into run mode. This provides the class with access to the equivalent of the Item object intrinsic to form code, as well as the Application object intrinsic to both form and VBA code. The AppointmentCode object variable declared at module level is used to keep the reference to the class alive so that it doesn’t close and go out of scope after the initialization code finishes running. The StopAppointmentCode macro is placed in the same module as the RunAppointmentCode macro and is used to release the AppointmentCode class when you are finished using it. Sub StopAppointmentCode() Set AppointmentTestCode = Nothing End Sub Developing and Debugging Using VBA Most VBA code you develop for use as form code can be copied unchanged to the form code window and used as VBScript code. The exceptions to this rule are: ❑ Item event handlers: Use the standard Item event handlers listed in the VBA column of the table in the section “Using the Code Window” in your VBA code and translate the event handler declarations to the declarations listed in the Form Code column when porting the code to VBScript. Object declarations: All variables in VBScript are Variant types. VBScript does not have the typed variables available in VBA or other higher-level languages. Comment out any As clauses in variable declarations in the VBA code to translate the declarations into VBScript code. The use of Variants instead of typed declarations implies that all objects in VBScript code are late-bound and the object references are resolved at runtime. Constants: VBScript doesn’t recognize any Outlook constants or enumerations. Declare any Outlook constants you plan to use with Const declarations in the code, or use the numeric equivalent of the constant in your code. VBScript does recognize many of the VB-related constants such as vbYes and vbNo, which are used in the MsgBox function. Error handling: VBScript only supports the On Error Resume Next error-handling construct, so only use that error-handling construct when developing in VBA for eventual use as form code. ❑ ❑ ❑ Custom Form Walkthrough In this section, you create a custom appointment form and develop code for it in the Outlook VBA project. The walkthrough takes you from start to finish with the process of creating and deploying a custom form, including opening a new form in design mode, adding controls to the form and binding the controls to Outlook properties, to publishing and running the form. You also learn about the choices of where to publish a form, how to publish a form using code, and how to test the code for a form. 124
Slide 150: Chapter 5: Outlook Forms Creating, Publishing, and Running a Form In this section, you follow the steps required to create a custom form, add controls to the form and bind the controls to Outlook UserProperties, and then publish and run the form. 1. 2. 3. 4. 5. Open an appointment form by selecting Tools ➪ Forms@@Design a Form. Then, select Appointment from the Standard Forms Library in the Design Form dialog. Select the P.2 page of the form. The default Appointment and Scheduling pages can’t be customized in an appointment form, as shown in the table in the section “Form Customization.” Display the Control Toolbox by clicking the Control Toolbox icon in the Tools section of the Ribbon’s Developer tab. Drag a CheckBox control to the form. Right-click the CheckBox control you just added and select Properties. Set its properties to the following settings: CheckBox Property Name Caption Top Left Height Width Properties Tab Display Display Layout Layout Layout Layout Value chkTravelTime Travel Time 40 48 24 144 6. 7. 8. 9. 10. Select the Value tab in the Properties dialog for the CheckBox control and click New. Set the Name of the field to TravelTime, set the Type to Yes/No, set the format to True/False, and click OK. This saves the new property as an Outlook UserProperty. Click OK to close the Properties dialog and accept the settings. Open the Field Chooser and click New. In the New Field dialog, set the Name to TravelTo, set the type to Duration, and leave the default format as 12h. Click OK to save the new property. Click New again, and in the New Field dialog set the Name to TravelFrom, set the type to Duration, and leave the default format as 12h. Click OK to save the new property. Drag TravelTo from the Field Chooser to the form, and then drag TravelFrom from the Field Chooser to the form. Each field dragged to the form adds both a Label control and a TextBox control. 125

   
Time on Slide Time on Plick
Slides per Visit Slide Views Views by Location