Biblia VBA Access
Biblia VBA Access
Helen Feddema
Helen Feddema
Access 2007 VBA Bible: For Data-Centric Microsoft Office Applications Published by Wiley Publishing, Inc. 10475 Crosspoint Boulevard Indianapolis, IN 46256 www.wiley.com Copyright 2007 by Wiley Publishing, Inc., Indianapolis, Indiana Published by Wiley Publishing, Inc., Indianapolis, Indiana Published simultaneously in Canada ISBN: 978-0-470-04702-6 Manufactured in the United States of America 10 9 8 7 6 5 4 3 2 1 No part of this publication may be reproduced, stored in a retrieval system or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, scanning or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States Copyright Act, without either the prior written permission of the Publisher, or authorization through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222 Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 646-8600. Requests to the Publisher for permission should be addressed to the Legal Department, Wiley Publishing, Inc., 10475 Crosspoint Blvd., Indianapolis, IN 46256, (317) 572-3447, fax (317) 572-4355, or online at http://www.wiley.com/go/permissions. Limit of Liability/Disclaimer of Warranty: The publisher and the author make no representations or warranties with respect to the accuracy or completeness of the contents of this work and specifically disclaim all warranties, including without limitation warranties of fitness for a particular purpose. No warranty may be created or extended by sales or promotional materials. The advice and strategies contained herein may not be suitable for every situation. This work is sold with the understanding that the publisher is not engaged in rendering legal, accounting, or other professional services. If professional assistance is required, the services of a competent professional person should be sought. Neither the publisher nor the author shall be liable for damages arising herefrom. The fact that an organization or Website is referred to in this work as a citation and/or a potential source of further information does not mean that the author or the publisher endorses the information the organization or Website may provide or recommendations it may make. Further, readers should be aware that Internet Websites listed in this work may have changed or disappeared between when this work was written and when it is read. For general information on our other products and services or to obtain technical support, please contact our Customer Care Department within the U.S. at (800) 762-2974, outside the U.S. at (317) 572-3993 or fax (317) 572-4002. Library of Congress Cataloging-in-Publication Data: Feddema, Helen Bell. Access 2007 VBA Bible: For Data-Centric Microsoft Office Applications / Helen Feddema. p. cm. Includes index. ISBN 978-0-470-04702-6 (paper/website) 1. Microsoft Access. 2. Database management. 3. Microsoft Visual Basic for applications. I. Title. QA76.9.D3F435 2007 005.7565--dc22 2007007061 Trademarks: Wiley, the Wiley logo, and related trade dress are trademarks or registered trademarks of John Wiley & Sons, Inc. and/or its affiliates, in the United States and other countries, and may not be used without written permission. Microsoft and Access are trademarks or 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.
Credits
Executive Editor Bob Elliott Development Editor Kelly Talbot Technical Editor Mary Hardy Production Editor Eric Charbonneau Copy Editor Kim Cofer Editorial Manager Mary Beth Wakefield Production Manager Tim Tate Vice President and Executive Group Publisher Richard Swadley Vice President and Executive Publisher Joseph B. Wikert Project Coordinator Adrienne Martinez Graphics and Production Specialists Sean Decker Jennifer Mayberry Heather Pope Amanda Spagnuolo Quality Control Technicians Melanie Hoffman Robert Springer Brian Walls Proofreading and Indexing Aptara Anniversary Logo Design Richard Pacifico
vii
Contents
Part II: Writing VBA Code to Exchange Data between Office Components
87
viii
Contents
ix
Contents
Working with XML and HTML Files ................................................................................338 Importing HTML and XML Files ............................................................................338 Exporting HTML and XML Files..............................................................................341 Emailing Exported Text Files ............................................................................................348 Summary ..........................................................................................................................350
427
Contents
The Extras Add-in Code ....................................................................................................483 Extras Options ........................................................................................................483 basExtras Module ....................................................................................................486 Back up Database ....................................................................................................489 Back up Back End Database ....................................................................................491 List Query Fields ....................................................................................................495 List Table Fields ......................................................................................................497 Other Procedures ....................................................................................................499 Finalizing the Add-in ..............................................................................................502 Troubleshooting Add-ins ..................................................................................................503 Interpreting Add-in Error Messages ........................................................................506 Installing an Add-in ..........................................................................................................507 Using the Extras 2007 Add-in ..........................................................................................510 Extras Options ........................................................................................................511 Back up Database ....................................................................................................512 Back up Database Back End ....................................................................................512 List Query Fields ....................................................................................................513 List Table Fields ......................................................................................................514 Summary ..........................................................................................................................514
Chapter 15: Customizing the Ribbon with XML in Access Databases and Add-ins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Useful Tools for Creating and Editing XML Code ..............................................................517 XML Notepad 2007 ................................................................................................517 VB 2005 XML Editor ..............................................................................................518 Office 2007 Custom UI Editor ................................................................................518 Customizing the Ribbon in an Access Database ................................................................519 Creating the XML Code ..........................................................................................522 Adding a New Tab, Group, and Controls to the Ribbon ................................527 Removing a Tab or Group from the Ribbon ..................................................528 VBA Code................................................................................................................542 Form Ribbons..........................................................................................................547 Customizing the Ribbon with an Access Add-in ................................................................549 Summary ..........................................................................................................................558
Chapter 16: Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
Preparing to Write a Visual Studio Add-in ........................................................................559 Adding .NET Support to Office ........................................................................................559 Modifying the Connect Class Module Code ............................................................573 Adding Functionality to the Shared Add-in ............................................................575 Debugging the Add-in ............................................................................................582 Building and Installing the Add-in ..........................................................................583 Using the Add-in ....................................................................................................588 Summary ..........................................................................................................................589
xi
Contents
Chapter 17: Creating Standalone Scripts with Windows Script Host . . . . 591
Tools for Working with Windows Script Host Scripts ........................................................591 The Microsoft Script Editor ....................................................................................592 The VBScript Help File ............................................................................................597 Differences between VBA and VBScript Code ....................................................................599 Useful Scripts ....................................................................................................................603 Setup Scripts ..........................................................................................................603 Office Scripts ..........................................................................................................611 Miscellaneous Scripts ..............................................................................................616 Scheduling a Backup Script with the Windows Vista Task Scheduler ................................619 Summary ..........................................................................................................................626
Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 673
xii
any thanks to my technical editor, Mary Hardy, for catching errors and making many valuable suggestions that improved the quality of the book (and sample databases), and to editors Kelly Talbot and Brian Hermann for their suggestions and support.
xiii
elcome to the Access 2007 VBA Bible. Like all books in the Bible series, you can expect to find both hands-on tutorials and real-world practical application information, as well as reference and background information that provides a context for what you are learning. This book is a fairly comprehensive resource on writing VBA code to exchange data among the main Office applications (Access, Word, Excel, and Outlook), using Access as the central application for storing data, and using the other applications for producing attractively formatted documents of various types. By the time you have completed the Access 2007 VBA Bible, you will be well-prepared to write VBA Automation code that uses your Access data to produce Word letters, mailing labels and other documents (without the overhead of mail merge), to create Excel worksheets and PivotCharts, and to create Outlook appointments, mail messages, contacts, and journal items, with or without attachments. Additionally, you will be able to synchronize contact data (both ways) between Access and Outlook.
xv
Introduction
Part III covers topics that add more functionality to Office, including the creation of COM add-ins with VB 6, Access add-ins, and Visual Studio 2005 Shared add-ins. It also covers customizing the Office 2007 Ribbon with XML in Access databases and with add-ins of various types. Additionally, there is a chapter on creating standalone scripts with Windows Script Host, and another chapter on using Access as a front end for working with SQL Server data.
CAUTION
This information is important and is set off in a separate paragraph with a special icon. Cautions provide information about things to watch out for, whether simply inconvenient or potentially hazardous to your data or systems. Tips generally are used to provide information that can make your work easierspecial shortcuts or methods for doing something easier than the norm. Notes provide additional, ancillary information that is helpful, but somewhat outside of the current presentation of information. New features introduce components or functionality that are new or improved in the software compared to earlier versions.
TIP
NOTE
NEW FEATURE
Minimum Requirements
To run the sample code in this book, you need a computer capable of running at least Windows XP, and of course you need Office 2007. Because Office 2007 runs fine on Windows XP, you dont need Windows Vista, but Office 2007 works even better on Vista. If you intend to run Vista, you need a Vista-ready computer. If you buy a new computer, look for the Windows Vista Capable sticker; however, an older computer may support Vista even if it doesnt have the sticker (though probably not the Aero Glass interface, which requires a high-powered video card).
xvi
Introduction
xvii
ince its earliest days about 14 years ago Access has been a relational database program, storing data in tables and using its own queries, forms, and reports to sort, filter, display, and print data. With successive Office versions, moving data among Office components (especially Word, Excel, and Outlook) has become so much easier that it is now often more efficient to use another Office component rather than an Access report for a task such as printing letters or analyzing numeric data. Additionally, using other Office components to display or print data from Access makes the data stored in Access tables more widely accessible. Many Office users have an edition of Office that doesnt include Access but they all have Word and Excel, and many also have Outlook, so they can easily work with Word documents, Outlook messages or appointments, and Excel worksheets, filled with data from Access tables. Whether you plan to present your data as an Access report, PivotChart, or PivotTable; or a Word document or Excel worksheet, the data is stored in Access tables, and entered and edited in Access forms.
IN THIS CHAPTER
A brief history of Office data exchange Storing data in Access Displaying Access data in forms and reports Working with rich text in Memo fields New report interactivity Using Access as a control center for working with Office documents
Part I
called Object Linking and Embedding [OLE], then OLE Automation) and Extensible Markup Language (XML). In early Windows and Office versions, DDE and ODBC were difficult to use, cranky and unreliable in operation, and ODBC in particular often required elaborate setup. I know I used both DDE and ODBC, when they were the only connectivity tools available. But I gladly dropped them when OLE became available in Windows 95/Office 95, because it offered a much simpler way to connect Office applications, though at first only in a limited manner. Before Office 97, there was a distinction between Office components that were OLE servers, which could be manipulated by code running from other applications, and OLE clients, which could work with objects in OLE server applications object models. Back in the days of Access 1.0 or even 2.0, Access developers had few tools available for connecting to other Office applications such as Word or Excel. Access, for example, was only a client, whereas Word was only a server. In Office 95 AccessBasic was upgraded to standard Office VBA, and Access became an OLE server (previously it was only an OLE client). By Office 2000, all the major Office applications (Access, Word, Excel, Outlook, and PowerPoint) had been upgraded to support Automation both as clients and servers, so the OLE server/client distinction is no longer significant. You can write Automation code in any major Office application to connect to any other Office applications data and functionality (and some third-party applications as well).
CROSS-REF
One valid exception is storing data in SQL Server back-end databases, using Access as the front end. SQL Server is usually the choice for huge corporate databases, not smallto medium-sized databases used by individuals or small companies, where Access can easily handle the number of records. See Chapter 18 for more information on this option.
Data entry and editing, too, should be done in Access, for the most part, because you can create Access forms that offer an attractive interface for entering and editing data. You can write VBA code that runs from form and control events for purposes of error handling, and create functions that automate repetitive data-processing operations. In my earlier book, Expert One-on-One Microsoft Application Development, I discussed creating Access applications, with details on using queries, forms, reports, and code. I wont duplicate this information here, but instead in this chapter I concentrate on new or improved features in Access 2007, which enhance the utility of Access forms and reports.
NEW FEATURE
Over the years, Access forms and reports have been significantly upgraded. In Access 2007, one long-requested feature has finally arrived in a workable form (I recall an early and unreliable implementation that made a brief appearance in Access 95). Memo fields can now store and display rich text, using the Text Align property, which takes a value of either Plain Text or Rich Text. When you select Rich Text for this property, you can apply various fonts, colors, and other attributes to selected portions of text in a table field or a control bound to that field.
In earlier versions of Office, if you wanted to generate a letter or other document including a block of text with color, bolding, or other attributes applied to selected words or phrases within the block, you had to create a Word letter and use Words formatting features. In Access 2007, you can produce Access reports with varied formatting within text blocks, displaying text entered into Access memo fields in a textbox on a form.
NOTE
To create a field that can store data in Rich Text format (behind the scenes, this is done using HTML code, but you dont have to worry about writing the code), start by creating a table field of the Memo data type, and selecting Rich Text as the Text Format value (see Figure 1.1).
Part I
WARNING
If you select a block of text in a Rich Textenabled Memo field, you will see a floating toolbar that lets you apply some formatting, including indenting or outdenting, similar to Word (this works fine; see Figure 1.2). However, if you apply bullets or numbering from this toolbar, you will get the bullets or numbers, but the text that runs over one line wont be indented properly, as shown in Figure 1.3. For this reason, I recommend against using these features, unless all the items on your lists are no more than one line in length.
FIGURE 1.2 Indenting text from the floating toolbar in a Rich Text field.
FIGURE 1.3 Incorrect indentation of an item in a numbered list in a Rich Text field.
Create a form bound to the table with the Memo field; you can now enter data into this textbox control and format it with different fonts, color, bolding, and other attributes, as you would while working in a Word document. When you place the cursor into a control with Rich Text enabled, a formatting group on the Form Ribbon is enabled, with a variety of formatting selections, as shown in Figure 1.4. Figure 1.5 shows the form with a variety of formatting attributes applied to the text in the textbox.
FIGURE 1.4 Selecting formatting for a portion of text in a Rich Textenabled textbox on a form.
FIGURE 1.5 A Rich Textenabled textbox with a variety of formatting applied to portions of its text.
Part I
Once you have applied formatting to the text on a form, you can create a report based on the table, and the formatting will display on the report as well (see Figure 1.6).
FIGURE 1.6 A report showing Rich Text formatting applied in a textbox on a form.
TIP
You can copy and paste formatted text from a Word document into a Memo field with Rich Text enabled (or a textbox bound to such a field), and the formatting will be preserved. However, bullets and numbered lists wont be aligned correctly, so it is best to turn off those features before copying text to Access.
I also created a one-row table to hold information to use in the database; in this case, it has two Rich Textenabled Memo fields for the letter header and signature information. The form fdlgSelectLetter (bound to the information table, zstblInfo) lets you edit the header and signature (Figure 1.8) and select a letter type and a contact.
FIGURE 1.8 A dialog form for editing header and signature block information and selecting a letter and contact.
Part I
Clicking the Create Letter button opens a filtered Access report displaying name and address data from the selected Contact record, and the letter body from the selected letter type, as depicted in Figure 1.9. With Rich Text formatting now supported in Access forms and reports, you may not need to produce a Word letter to get the look you want in printed documents. However, compared with Word, Access reports using Rich Textenabled Memo fields have one significant limitation. In Word, you can place merge fields or DocProperty fields within a block of text, so that merged data or data stored in document properties will print at a certain point in the text, with the surrounding text wrapping as needed, depending on the length of the text in the fields. This is not possible with a Memo field on an Access report, so if you need to embed merge fields or DocProperty fields within the letter body text, you still need to create Word documents.
CROSS-REF
FIGURE 1.9
See Chapter 2 for information on creating Word documents of various types filled with Access data.
10
first realized what a problem it is to work in a database with no naming convention when I took over a database created by another programmer. The database had a table, a query, a form, a report, a function, and five or six variables (of different data types) all called Sales (this was only one of a number of sets of objects with the same name). This meant that when I encountered the word Sales in VBA code, I had no idea whether it was a reference to a table, form, query, function, or variable, unless the context made it clear. There were numerous errors because of the use of the same name for different types of objects, because (among other possible sources of errors) you can set the value of a field with a variable, or with a function and if several variables and a function are all called Sales, the code may use the wrong one. You can also get circular reference errors when a control has the same name as the field to which it is bound which is still the case in Access 2007, when you create a form bound to a table using the Form Wizard. To prevent these reference errors, it is an excellent idea to use a naming convention for database objects, controls, and variables. Using a naming convention also makes your database selfdocumenting (to some extent, at least), and prevents confusion when selecting an object from a drop-down list. About 10 years ago, I wrote an Access 97 add-in (LNC Rename.mda) to automate the process of giving the appropriate tags to database objects and controls on forms and reports, using the Leszynski Naming Convention (LNC). This add-in was updated for Access 2000, and that version still works in Access 2007; it is available as Code Sample #10 (for Access 2000 or higher databases) from the Code Samples page of my web site, www.helenfeddema.com.
NOTE
The query qryNorthwindAll links all the tables in the Modified Northwind database. To produce a plain datasheet report, start by selecting qryNorthwindAll, and selecting Report Wizard in the Reports group on the Create tab of the Ribbon, as shown in Figure 1.10.
11
Part I
Select the fields to include on the report (see Figure 1.11) and click Next.
12
On the next screen of the wizard (see Figure 1.12), select the top-level grouping you want for the report (in this case, I accepted the default selection of OrderQuarter, Customer, OrderDate).
The next screen lets you select sub-groups, if desired; I accepted the default (no further subgrouping, as shown in Figure 1.13).
13
Part I
Next, select sorting and summarizing options; I selected ProductName for sorting, as shown in Figure 1.14.
On the report layout screen, depicted in Figure 1.15, I selected the Block option.
14
Select a style on the next screen, shown in Figure 1.16 (bear in mind that some of the more artistic styles wont look good when printed on a black-and-white printer). I selected None for a plain report.
Give the report a name on the Finish screen (Figure 1.17) I called it rptNorthwindSales. Select the Modify the reports design option to open the report in design view, and click Finish.
15
Part I
TIP
You will need to make some adjustments to the reports design in the property sheet; if it is not open, click the Property Sheet button in the Tools group on the Design tab of the Ribbon, as shown in Figure 1.18.
By default, in the Block report layout only the cells with values have visible borders, so to create an overall worksheet-type layout, select all the controls in the Detail section of the report, and turn off display of duplicate data values by setting the Hide Duplicates property to No on the Format tab of the properties sheet, as shown in Figure 1.19.
FIGURE 1.19 Setting the Hide Duplicates property to No to eliminate duplicate data on a report.
Although it is generally not a problem for report controls to have the same names as their bound fields (because they are rarely, if ever, referenced in code), I like to give the appropriate tags to the bound controls at least. To do this manually, give the textboxes the prefix txt. (On a report created by the Report Wizard, labels are named with the non-standard suffix _Label.)
16
NEW FEATURE
The Report Wizard generally applies the default alignment to each column label, which may result in inconsistent alignment. That doesnt look good, so (if necessary) adjust the alignment of the column headings labels as desired (generally they should be either all left-aligned or all centered); on the sample report I made them all centered.
If you need to adjust an individual column width, it may be necessary to turn off control grouping (a new feature in Access 2007). To accomplish this, first select the controls in the Detail section and click the yellow group anchor that should now be visible (though not very visible Microsoft should have selected a color with more contrast than dull mustard yellow) at the upper-left corner of the group, then right-click any control in the group and select Remove from the Layout submenu (see Figure 1.20).
Figure 1.21 shows the finished worksheet-type report. You can interactively sort and filter a report in report view for example, when you want to view records from only one customer, as shown in Figure 1.22.
17
Part I
18
After making the selection, the report shows only Gourmet Lanchonetes records (Figure 1.23).
PivotTables
If you need more advanced interactivity, you can make a PivotTable based on the same qryNorthwindAll query. In Access 2007, the process of creating a PivotTable has been simplified; just select the data source query or table, then, as depicted in Figure 1.24, select PivotTable from the More Forms menu of the Forms group in the Create tab of the Ribbon.
19
Part I
A new form based on the data source opens in PivotTable view (see Figure 1.25), with the Field List open so you can drag fields to the drop zones of the PivotTable, one field each to the Row Fields, Column Fields, and (optionally) Filter Fields drop zones, which are indicated in gray text at the upper-left corner of the form. The data field to be displayed in the body of the table is dragged to the drop zone in the center of the form; Access will automatically create a Count or Sum field when appropriate.
CROSS-REF
See Chapter 5 of my book, Expert One-on-One Microsoft Application Development, for more information on creating and using PivotTables and PivotCharts.
Figure 1.26 shows the PivotTable with Salesperson selected as the Row field and CategoryName as the Column field, with Price as the Totals field.
20
21
Part I
PivotCharts
Making a PivotChart is even easier: select the data source query or table and click the PivotChart button in the Forms group in the Create tab of the Ribbon. Figure 1.27 shows the new, blank PivotChart with drop zones at the top and right sides of the form.
As with a PivotTable, you simply drag fields from the field list to the appropriate drop zones. I dragged the OrderQuarter field to the Category field drop zone, the Supplier field to the Series drop zone, the CategoryName to the Filter drop zone, and the Price field to the Data drop zone, and I selected the Dairy Products category for filtering the data. Figure 1.28 shows the PivotChart at this point.
22
FIGURE 1.28 The PivotChart with fields assigned to its drop zones.
The next step is to give names to the charts vertical and horizontal axes. To name the axes, select an Axis Title label, open the property sheet, and select its Format tab; enter the name you want to display for that axis in the Caption property, as shown in Figure 1.29. Repeat for the other axis label.
23
Part I
To make the charts Legend visible (generally a good idea), click the Legend button in the Show/Hide group of the Design tab of the Ribbon, as shown in Figure 1.30.
Access 2007 reports have some interactivity, and PivotTables and PivotCharts have almost unlimited interactivity, but both have a serious limitation: the interactivity is available only when you are working in the Access database; when you send an Access report, PivotTable, or PivotChart to someone else who doesnt have Access, say as a PDF file, the recipient gets a read-only image of the report, PivotTable, or PivotChart, with no interactivity. This may be what you want in some cases; but if you need to deliver data in a worksheet or chart format that users can interact with, you need to create an Excel worksheet or chart from your Access data, rather than a report, PivotTable, or PivotChart.
24
CROSS-REF
Access data.
See Chapter 3 for information on creating Excel worksheets and charts filled with Access data, and Chapter 12 for information on creating Excel PivotCharts filled with
Summary
In this chapter you have learned about some new features of Access 2007, especially those related to producing Access reports to display data stored in Access tables. But sometimes you need to display your data in Word documents or Excel worksheets, rather than Access reports; or you may need to create Outlook appointments, tasks, contacts, or mail messages, using Access as a control center for working with Office documents. When you need to produce Word documents, Excel worksheets, or Outlook items, either to make use of the special features of these Office components or to distribute your data in formats that can be used by everyone who has Office, you have two options: use the built-in Export selections on the Ribbon, or write VBA code to create the Office documents and fill them with Access data. The next three chapters describe how to create Word documents, Excel worksheets or charts, and Outlook items, and fill them with Access data.
25
f you need to produce documents with more sophisticated formatting than is available in an Access report, your best option is to create Word documents and fill them with data from Access. There are many ways to export data from Access to Word; which one to use depends on the circumstances, the type of data to export, and user preferences. This chapter discusses the various types of Word documents you can create and the methods you can use to fill them with data, with basic examples.
For more complex and realistic examples of exporting Access data to Word documents, see Chapters 6 and 12. The sample database for this chapter is Access to Word.accdb.
IN THIS CHAPTER
Word documents and templates Working with Word bookmarks Working with the TypeText method Working with Word document properties Working with form fields Working with tables
CROSS-REF NOTE
Filling Word Documents with Access Data Using the TypeText Method
You can create a blank Word document (based on the default Word template) with two lines of code:
Set appWord = GetObject(, Word.Application) Set doc = appWord.Documents.Add
27
Part I
NOTE
In most of my Automation code working with other Office applications (Word, Excel, and Outlook), I use the GetObject function in the body of a procedure, to set a reference to the running instance of the application, if there is one; the procedures error handler runs CreateObject if the application is not already running (see the code samples later in this chapter for examples). This prevents creation of multiple instances of Word, Excel, or Outlook.
If you dont need any fancy formatting, just a plain text document, you can fill a blank Word document with text using the TypeText method. The FillWithTypeText procedure listed next creates a blank Word document, then enters a document heading, then reads text from fields in an Access table and writes it directly to the Word document, and finally applies some simple formatting, using Word commands:
Private Sub FillWithTypeText () On Error GoTo ErrorHandler Dim Dim Dim Dim appWord As Word.Application doc As Word.Document dbs As DAO.Database rst As DAO.Recordset
Insert a two-column table to hold contact data (one column for contact names, the other for user comments):
doc.Tables.Add Range:=Selection.Range, _ NumRows:=1, _ NumColumns:=2, _ DefaultTableBehavior:=wdWord9TableBehavior, _ AutoFitBehavior:=wdAutoFitFixed With appWord.Selection.Tables(1) If .Style <> Table Grid Then .Style = Table Grid End If
28
.ApplyStyleHeadingRows = True .ApplyStyleLastRow = False .ApplyStyleFirstColumn = True .ApplyStyleLastColumn = False .ApplyStyleRowBands = True .ApplyStyleColumnBands = False End With
29
Part I
TIP
If you dont know the VBA syntax for an operation, you can record a Word macro to create a procedure containing the code, though you may have to trim out the excess recorded macros generally set every single argument of a method, whether or not they are needed.
FIGURE 2.1 A Word document filled with Access data using the TypeText method.
30
Bookmarks
As an example of using bookmarks in a Word template, I created a template designed to print on a certain type of paper (Paper Direct Milano), with a header and footer, several fonts, and two sections, the first for the letter itself and the second for a matching COM 10 envelope. I placed several bookmarks in the letter portion of the template, where the letter date, name and address, and salutation should print, and cross-reference fields in the envelope portion, to print the name and address there, as well as a PostNet ZIP bar code.
WARNING
This chapter uses the new .dotx Word 2007 template format, which creates a document of the .docx type. Documents in this new Word format can only be used by people running Office 2007, so if you need to distribute Word documents to people running older versions of Office, it is best to use the .dot template format, and create .doc documents, which are usable in Office 97 through 2007.
ou can place Word UserName and UserAddress fields in the document header to print your name and address. If you have used these fields in previous versions of Word, you may have difficulty locating them in the new Word 2007 interface, especially because Microsoft has chosen to put them in different places (in Word 2003, they were all conveniently located on the User Information tab of the Options dialog). In Word 2007, the user name is entered in the User Name field on the Personalize page of the Word Options dialog, which is opened from the Word Options button on the File menu.
31
Part I
continued
Curiously, the user address information is entered in a different location, as Mailing address at the bottom of the Advanced page of the Word Options dialog.
Entering the mailing address (user address) in the Word Options dialog.
To place a field with the user name or user address into a template, select Field from the Quick Parts list in the Text group of the Insert menu.
32
This opens the Field dialog (similar to the one in Word 2003), where you can select the User Name or User Address field for insertion.
A template header with user name and address information from Word user information fields.
33
Part I
To insert a bookmark in a document or template, select Bookmark from the Links group on the Insert tab, as in Figure 2.2.
WARNING
the fields.
It is advisable to uncheck the Preserve formatting during updates checkbox when inserting fields, as otherwise you can get different fonts or sizes for the text displayed in
Type the bookmark name in the Bookmark dialog and click Add (Figure 2.3).
NOTE
By default, you wont see the I-beam markers that indicate bookmarks in a Word document; to see them, check the Show bookmarks checkbox on the Advanced page of the Word Options dialog, as depicted in Figure 2.4.
34
Figure 2.5 shows the second (envelope) page of the Milano Word template, with user information fields, bookmarks, and a BarCode field (to see these fields, press Alt+F9).
35
Part I
CROSS-REF
In code for this chapters sample database, the assumption is that the Word templates are located in the default folder, which is C:\Users\User Name\AppData\Roaming\ Microsoft\Templates for Windows Vista or C:\Documents and Settings\User Name\Application Data\Microsoft\Templates for Windows XP. For a more flexible method of setting the Templates folder, see Chapter 6.
The form frmContacts in the sample Access to Word database displays contacts located in the U.S.A. The form, shown in Figure 2.6, has three buttons, each of which creates a letter to the selected contact using a different method.
FIGURE 2.6 The Contacts form with buttons to create Word letters.
36
strRecipientZip As String strPrompt As String strTitle As String strSalutation As String fso As New Scripting.FileSystemObject fil As Scripting.File
lngContactID = Nz(Me![ContactID]) If lngContactID = 0 Then strPrompt = No contact selected strTitle = Problem MsgBox strPrompt, vbOKOnly + vbExclamation, strTitle GoTo ErrorHandlerExit Else strRecipientName = Nz(Me![FirstNameFirst]) strRecipientAddress = Nz(Me![RecipientAddress]) strSalutation = Nz(Me![FirstName]) strRecipientZip = Nz(Me![PostalCode]) End If Set appWord = GetObject(, Word.Application)
Get the default User Template path from the Word Options dialog (it is still available in code, though it has disappeared from the Word 2007 interface):
strTemplatePath = _ appWord.Options.DefaultFilePath(wdUserTemplatesPath) _ & \ Debug.Print Templates folder: & strTemplatePath strTemplateName = Milano Letter (Bookmarks).dotx strTemplateNameAndPath = strTemplatePath & strTemplateName On Error Resume Next
Try to locate the template in the default Templates folder, and put up a message if it is not found:
Set fil = fso.GetFile(strTemplateNameAndPath) If fil Is Nothing Then strPrompt = Cant find & strTemplateName _ & in & strTemplatePath & ; canceling MsgBox strPrompt, vbCritical + vbOKOnly GoTo ErrorHandlerExit End If On Error GoTo ErrorHandler Set doc = appWord.Documents.Add(strTemplateNameAndPath)
37
Part I
Reinsert the EnvelopeZip bookmark so the zip code will be available for use by the bar code field on the envelope:
.MoveLeft Unit:=wdWord, Count:=3, Extend:=wdExtend doc.Bookmarks.Add Range:=Selection.Range, _ Name:=EnvelopeZip .Goto what:=wdGoToBookmark, Name:=LetterText strPrompt = Ready to enter letter text strTitle = Access data imported MsgBox strPrompt, vbOKOnly, strTitle appWord.Visible = True appWord.Activate End With ErrorHandlerExit: Set appWord = Nothing Exit Sub ErrorHandler: If Err = 429 Then
38
Document Properties
When you use Word document properties rather than bookmarks to write Access data to a Word document, you dont need to have two (or more) sets of bookmarks, one for each place you want to display a piece of data (for example, displaying the recipients name and address on the letter and the envelope). You can write the data to a document property once and display it in multiple places in the Word document with DocProperty fields. To create the properties, first select Finish from the File menu in the template, and select Properties, as shown in Figure 2.7.
39
Part I
This opens the Document Information panel, with a few of the more common document properties displayed (see Figure 2.8).
To get to the Properties sheet, select Advanced from the Properties drop-down at the top of the panel; this opens the Properties sheet, which looks much the same as in Word 2003. Click the Custom tab (Figure 2.9) to start creating document properties.
To create a document property, enter its name (no spaces), select the type, enter a value (a space will do for a Text property), and then click Add. Figure 2.10 shows the doc properties for the Milano Letter (Doc Props) template.
40
FIGURE 2.10 The Doc properties for the Milano Letter (Doc Props) template.
Next, place DocProperty fields in the template where you want the values in the doc properties to display.
NOTE
One bookmark is still needed, even if you are using doc properties to display data from Access: The BarCode field needs to reference a bookmark in order to create the PostNet bar code. Therefore, after placing a DocProperty field for the zip into the template, select that field and create a bookmark for it.
To insert a DocProperty field, select QuickParts from the Text group on the Insert tab of the Ribbon, select DocProperty as the field type, and then select the doc property from the Property list (see Figure 2.11).
TIP
Dont give your doc properties the same names as any built-in properties, although Word lets you do this, because this will make it difficult to select the correct property from the list of properties, which includes both built-in and custom doc properties.
Figure 2.12 shows the first page of the template, with DocProperty fields displayed, and one bookmark to indicate the place where the letter text is to be typed.
41
Part I
FIGURE 2.12 The first page of the Milano Letter (DocProps) template, with User information and DocProperty fields.
42
The code that creates the document with text written to doc properties is listed as follows:
Private Sub cmdDocProps_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim appWord As Word.Application doc As Word.Document dbs As DAO.Database rst As DAO.Recordset strTemplatePath As String strTemplateName As String strTemplateNameAndPath As String lngContactID As Long strRecipientName As String strRecipientAddress As String strRecipientZip As String strPrompt As String strTitle As String strSalutation As String fso As New Scripting.FileSystemObject fil As Scripting.File prps As Object
lngContactID = Nz(Me![ContactID]) If lngContactID = 0 Then strPrompt = No contact selected strTitle = Problem MsgBox strPrompt, vbOKOnly + vbExclamation, strTitle GoTo ErrorHandlerExit Else strRecipientName = Nz(Me![FirstNameFirst]) strRecipientAddress = Nz(Me![RecipientAddress]) strSalutation = Nz(Me![FirstName]) strRecipientZip = Nz(Me![PostalCode]) End If Set appWord = GetObject(, Word.Application)
Get the default User Templates path from the Word Options dialog:
strTemplatePath = _ appWord.Options.DefaultFilePath(wdUserTemplatesPath) _ & \ Debug.Print Templates folder: & strTemplatePath strTemplateName = Milano Letter (Doc Props).dotx strTemplateNameAndPath = strTemplatePath & strTemplateName On Error Resume Next
43
Part I
Try to locate the template in the default Templates folder, and put up a message if it is not found:
Set fil = fso.GetFile(strTemplateNameAndPath) If fil Is Nothing Then strPrompt = Cant find & strTemplateName _ & in & strTemplatePath & ; canceling MsgBox strPrompt, vbCritical + vbOKOnly GoTo ErrorHandlerExit End If On Error GoTo ErrorHandler Set doc = appWord.Documents.Add(strTemplateNameAndPath)
Update fields:
With appWord .Selection.WholeStory .Selection.Fields.Update .Selection.HomeKey unit:=wdStory End With appWord.Selection.Goto what:=wdGoToBookmark, _ Name:=LetterText strPrompt = Ready to enter letter text strTitle = Access data imported MsgBox strPrompt, vbOKOnly, strTitle appWord.Visible = True appWord.Activate ErrorHandlerExit: Set appWord = Nothing Exit Sub ErrorHandler: If Err = 429 Then
44
Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Sub
TIP
If you dont see the Developer tab, turn it on by checking the Show Developer tab in the Ribbon checkbox on the Personalize page of the Word Options dialog (Figure 2.14).
To add the choices for a drop-down or combo box list, switch to Design mode by clicking the Design button in the Controls group, select the control, and then select Properties from the Controls group on the Developer tab, as depicted in Figure 2.15.
45
Part I
46
Add the choices for the drop-down list in the Properties sheet (see Figure 2.16). To add a choice, click the Add button, enter the display name and value, and click OK; repeat for each selection you want to add to the list.
TIP
Dont check the Contents cannot be edited checkbox; if you do, selections cant be made from the drop-down list.
When a new document is created from a template with content controls, when you hover the mouse over a content control, the title appears over the control, and you can click the drop-down arrow to open the list and select an item (Figure 2.17).
47
Part I
Summary
This chapter covered three methods that you can use to write Access data to Word documents (TypeText, bookmarks, and doc properties). With the aid of these techniques, and a set of preformatted Word templates, you can make use of the superior data storage and editing capabilities of Access and the superior document formatting properties of Word.
48
IN THIS CHAPTER
Exporting Access data to Excel using a Ribbon command Creating new Excel worksheets from templates Formatting Access data exported to an Excel worksheet
NOTE
49
Part I
Clicking the Excel command with a database object selected opens the Export dialog, where you can browse for the folder where the worksheet should be saved, and select a worksheet file format. This dialog is shown in Figure 3.2. You can check the Export data with formatting and layout selection if desired, but it doesnt make much of a difference when exporting data from tables or queries, and I dont recommend exporting data from forms or reports, because the formatting you need in Excel isnt the same as the formatting you need in an Access form or report.
50
FIGURE 3.2 The Export dialog opened from the Excel command.
Using qryNorthwindAll as the data source, you get the plain worksheet shown in Figure 3.3.
FIGURE 3.3 An Excel worksheet created from data in the qryNorthwindAll Access query.
51
Part I
TIP
Using a query as a data source allows you to combine data from multiple tables and also to format data as you want it to appear in the target worksheet, using data type conversion functions such as CDate, CCur, or CStr.
With a few clicks, you can resize the worksheet columns as needed, edit the column headers as needed, and make the column header row bold, and a plain but serviceable worksheet (Figure 3.4) is ready for use.
FIGURE 3.4 The exported worksheet with a little formatting applied manually.
Using Excel Templates to Create Formatted Worksheets Filled with Access Data
If you want to produce a more formatted worksheet, you can prepare an Excel template and format it as needed for example, adding a large, centered title and column headings with appropriate text, perhaps in a larger or bolder font than the data area. Then, instead of using the Excel command on the Ribbon, use VBA code to export the Access data row by row to the data area of a new worksheet created from the template. I created a set of queries for archiving data, again using the sample Northwind data, and a dialog form (fdlgArchiveOrders) that allows the user to select a date range for archiving Orders data, as shown in Figure 3.5.
NEW FEATURE
Note the calendar icon next to the date controls (it appears when a control bound to a Date field has the focus). Clicking the icon opens a calendar for selecting a valid date, as shown in Figure 3.6. The new calendar pop-up is definitely useful, though selecting a date far in the past can be tedious, because there is no way to move year by year.
52
FIGURE 3.5 A dialog form for selecting Northwind Orders data to archive.
Once the start date and end date have been entered or selected, clicking the Archive button runs a procedure that creates a new Excel worksheet from a template (Orders Archive.xltx) in the same folder as the database, fills it with data from tblOrders in the selected date range, and deletes the archived records. The ArchiveData procedure uses the Start Date and End Date values selected in the dialog as arguments. This procedure is listed as follows, together with the CreateAndTestQuery procedure it uses to create a query programmatically, and another procedure (TestFileExists) that tests whether a file exists in a specific folder:
Public Sub ArchiveData(dteStart As Date, dteEnd As Date) On Error GoTo ErrorHandler Dim Dim Dim Dim appExcel As Excel.Application intReturn As Integer lngCount As Long n As Long
53
Part I
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim
rng As Excel.Range rngStart As Excel.Range strDBPath As String strPrompt As String strQuery As String strSaveName As String strSheet As String strSheetTitle As String strSQL As String strTemplate As String strTemplateFile As String strTemplatePath As String strTitle As String wkb As Excel.Workbook wks As Excel.Worksheet
Create a new worksheet from the template and export the Access data to it:
strDBPath = Application.CurrentProject.Path & \ Debug.Print Current database path: & strDBPath
54
strTemplate = Orders Archive.xltx strTemplateFile = strDBPath & strTemplate If TestFileExists(strTemplateFile) = False Then
Write data from the recordset to the data area of the worksheet, using the columnoffset argument to move to the next cell:
55
Part I
rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value
= Nz(rst![OrderID]) rng.Offset(columnoffset:=1) = Nz(rst![Customer]) rng.Offset(columnoffset:=1) = Nz(rst![Employee]) rng.Offset(columnoffset:=1) = Nz(rst![OrderDate]) rng.Offset(columnoffset:=1) = Nz(rst![RequiredDate]) rng.Offset(columnoffset:=1) = Nz(rst![ShippedDate]) rng.Offset(columnoffset:=1) = Nz(rst![Shipper]) rng.Offset(columnoffset:=1) = Nz(rst![Freight]) rng.Offset(columnoffset:=1) = Nz(rst![ShipName]) rng.Offset(columnoffset:=1) = Nz(rst![ShipAddress]) rng.Offset(columnoffset:=1) = Nz(rst![ShipCity]) rng.Offset(columnoffset:=1) = Nz(rst![ShipRegion]) rng.Offset(columnoffset:=1) = Nz(rst![ShipPostalCode]) rng.Offset(columnoffset:=1) = Nz(rst![ShipCountry]) rng.Offset(columnoffset:=1) = Nz(rst![Product]) rng.Offset(columnoffset:=1) = Nz(rst![UnitPrice]) rng.Offset(columnoffset:=1) = Nz(rst![Quantity]) rng.Offset(columnoffset:=1) = Nz(rst![Discount])
Save and close the filled-in worksheet, using a workbook save name with the date range selected in the dialog:
strSaveName = strDBPath & strSheetTitle & .xlsx Debug.Print Time sheet save name: & strSaveName
56
Put up a success message, listing the name and path of the new worksheet:
strTitle = Workbook created strPrompt = Archive workbook & strSheetTitle & _ & vbCrLf & created in & strDBPath MsgBox strPrompt, vbOKOnly + vbInformation, strTitle
Delete the archived records, processing the many table first, because you cant delete a record in the one table if there are linked records in the many table:
DoCmd.SetWarnings False strSQL = DELETE tblOrderDetails.*, _ & tblOrders.ShippedDate _ & FROM tblOrderDetails INNER JOIN qryArchive _ & ON tblOrderDetails.OrderID = qryArchive.OrderID; Debug.Print SQL string: & strSQL DoCmd.RunSQL strSQL strSQL = DELETE tblOrders.* FROM tblOrders WHERE _ & [ShippedDate] Between # & dteStart & # And # _ & dteEnd & #; Debug.Print SQL string: & strSQL DoCmd.RunSQL strSQL
57
Part I
ErrorHandler: Excel is not running; open Excel with CreateObject If Err.Number = 429 Then Set appExcel = CreateObject(Excel.Application) Resume Next Else MsgBox Error No: & Err.Number & ; Description: Resume ErrorHandlerExit End If End Sub Public Function CreateAndTestQuery(strTestQuery As String, _ strTestSQL As String) As Long
This function is called from other procedures to create a filtered query, using a SQL string in its strTestSQL argument:
On Error Resume Next Dim qdf As DAO.QueryDef Delete old query Set dbs = CurrentDb dbs.QueryDefs.Delete strTestQuery On Error GoTo ErrorHandler Create new query Set qdf = dbs.CreateQueryDef(strTestQuery, strTestSQL) Test whether there are any records Set rst = dbs.OpenRecordset(strTestQuery) With rst .MoveFirst .MoveLast CreateAndTestQuery = .RecordCount End With ErrorHandlerExit: Exit Function ErrorHandler: If Err.Number = 3021 Then CreateAndTestQuery = 0 Resume ErrorHandlerExit Else MsgBox Error No: & Err.Number & ; Description: & Err.Description Resume ErrorHandlerExit End If
58
End Function Public Function TestFileExists(strFile As String) As Boolean On Error Resume Next TestFileExists = Not (Dir(strFile) = ) End Function
NOTE
The code in the sample database requires a reference to the Excel object library; Figure 3.7 shows this reference checked in the References dialog, which is opened from the Tools menu in the Visual Basic window.
After the worksheet of archived records has been created and saved, you will get a message (depicted in Figure 3.8) listing the location where the archive worksheet was saved.
59
Part I
NOTE
See Chapter 7 for a more flexible way of specifying a Templates folder and a Documents folder.
After the code deletes the archived records first the ones in tblOrderDetails (the many table) and then those in tblOrders (the one table) a final message appears, as shown in Figure 3.9.
FIGURE 3.9 A final informative message stating that the archived database records have been cleared.
Saving the newly created worksheet with the xlWorkbookDefault value for the FileFormat argument saves it as a standard Excel worksheet. If you need to save the worksheet in another format, perhaps for use by someone running an older version of Excel, you can use one of the other values in the XlFileFormat enum, which are shown in the Object Browser in Figure 3.11. The xlExcel9795 named constant will create a worksheet in a format usable by people running Excel 95 or 97. (The worksheet format choices available in VBA code are much more numerous than those available in the interface, as shown in Figure 3.12.)
60
FIGURE 3.11 Viewing the file format choices for saving an Excel workbook.
WARNING
If you create a worksheet in the new .xlsx format, only Office 2007 users will be able to open it. To create a worksheet that can be opened and edited by users with earlier versions of Office, select one of the other formats. The Excel 97Excel 2003 Workbook (.xls) format (shown being selected in Figure 3.12) is usable in Office 97 through 2007, so it is generally the most useful worksheet format.
61
Part I
NOTE
To open the Object Browser for examining components of an object model, open the Visual Basic window and select Object Browser from the View menu, or press F2.
CROSS-REF
See Chapter 7 for examples of creating worksheets using the TransferSpreadsheet method.
In this section, data from qryOrdersAndDetails is exported to a new worksheet made from a template and is then formatted in code. For convenience, the ExportNorthwindData procedure can be run from the macro mcrExportNorthwindData. The procedure starts by creating a new worksheet from a template (Northwind Orders.xltx), as for the ArchiveData procedure. Data from the query qryOrdersAndDetails is written to rows in the worksheet, and then a set of Excel commands is used to apply hairline borders to the data area, and a double bottom border to the column headings row. Next, the worksheets data area is sorted by the first two columns (Country and Category), and the extra values are removed (the effect is similar to turning on Hide Duplicates in an Access report). Finally, a Grand Total is created under the last row, made large and bold, and enclosed in a box. The procedure is listed as follows:
Public Sub ExportNorthwindData() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim appExcel As Object i As Integer lngCount As Long lngCurrentRow As Long lngRows As Long n As Long objFind As Object rng As Excel.Range rngData As Excel.Range rngStart As Excel.Range strCategory As String strCountry As String strCurrAddress As String strDBPath As String strFormula As String
62
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim
strPrompt As String strDataRange As String strRange As String strSaveName As String strSheetName As String strStartAddress As String strTemplate As String strTemplateFile As String strTitle As String wkb As Excel.Workbook wks As Excel.Worksheet
Create a new worksheet from the template and export data to it:
strDBPath = Application.CurrentProject.Path & \ Debug.Print Current database path: & strDBPath strTemplate = Northwind Orders.xltx strTemplateFile = strDBPath & strTemplate If TestFileExists(strTemplateFile) = False Then
63
Part I
Write data from the recordset to cells in the current row of the worksheet, using the columnoffset argument to move to the next cell: rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value Set rng = rng.Value = Nz(rst![ShipCountry]) rng.Offset(columnoffset:=1) = Nz(rst![Category]) rng.Offset(columnoffset:=1) = Nz(rst![Product]) rng.Offset(columnoffset:=1) = Nz(rst![Customer]) rng.Offset(columnoffset:=1) = Nz(rst![OrderID]) rng.Offset(columnoffset:=1) = Nz(rst![UnitPrice]) rng.Offset(columnoffset:=1) = Nz(rst![Quantity]) rng.Offset(columnoffset:=1) = Nz(rst![Discount]) rng.Offset(columnoffset:=1) = Nz(rst![TotalPrice])
Determine the number of data rows in the worksheet with the UsedRange property:
lngRows = wks.UsedRange.Rows.Count Debug.Print Number of data rows in worksheet: & lngRows
64
.Borders(xlEdgeLeft).LineStyle = xlContinuous .Borders(xlEdgeLeft).Weight = xlHairline .Borders(xlEdgeLeft).ColorIndex = xlAutomatic .Borders(xlEdgeTop).LineStyle = xlContinuous .Borders(xlEdgeTop).Weight = xlHairline .Borders(xlEdgeTop).ColorIndex = xlAutomatic .Borders(xlEdgeBottom).LineStyle = xlContinuous .Borders(xlEdgeBottom).Weight = xlHairline .Borders(xlEdgeBottom).ColorIndex = xlAutomatic .Borders(xlEdgeRight).LineStyle = xlContinuous .Borders(xlEdgeRight).Weight = xlHairline .Borders(xlEdgeRight).ColorIndex = xlAutomatic .Borders(xlInsideVertical).LineStyle = xlContinuous .Borders(xlInsideVertical).Weight = xlHairline .Borders(xlInsideVertical).ColorIndex = xlAutomatic .Borders(xlInsideHorizontal).LineStyle = xlContinuous .Borders(xlInsideHorizontal).Weight = xlHairline .Borders(xlInsideHorizontal).LineStyle = xlContinuous End With
65
Part I
wks.Range(strDataRange).Select wks.Sort.SortFields.Clear wks.Sort.SortFields.Add Key:=Range(strKey1Range), _ SortOn:=xlSortOnValues, _ Order:=xlAscending, _ DataOption:=xlSortNormal wks.Sort.SortFields.Add Key:=Range(strKey2Range), _ SortOn:=xlSortOnValues, _ Order:=xlAscending, _ DataOption:=xlSortNormal With wks.Sort .SetRange Range(strDataRange) .Header = xlYes .MatchCase = False .Orientation = xlTopToBottom .SortMethod = xlPinYin .Apply End With
66
67
Part I
With appExcel.Selection.Borders(xlEdgeRight) .LineStyle = xlContinuous .ColorIndex = 0 .TintAndShade = 0 .Weight = xlMedium End With With appExcel.Selection .Borders(xlInsideVertical).LineStyle = xlNone .Borders(xlInsideHorizontal).LineStyle = xlNone End With
Save and close the filled-in worksheet, using a workbook save name with the date range:
strSheetName = Northwind Orders as of _ & Format(Date, d-mmm-yyyy) Debug.Print Sheet name: & strSheetName
Put up a success message with the name and path of the new worksheet:
strTitle = Workbook created strPrompt = strSheetName & vbCrLf & created in _ & strDBPath MsgBox strPrompt, vbOKOnly + vbInformation, strTitle ErrorHandlerExit: Exit Sub ErrorHandler: Excel is not running; open Excel with CreateObject
68
If Err.Number = 429 Then Set appExcel = CreateObject(Excel.Application) Resume Next Else MsgBox Error No: & Err.Number & ; Description: _ & Err.Description Resume ErrorHandlerExit End If End Sub
FIGURE 3.13 A worksheet filled with data and formatted using VBA code.
Summary
When you need to export Access data to Excel worksheets so that everyone who has Office can work with them, you can use the techniques discussed in this chapter to export Access data in the interface, or using VBA code, either to a plain default worksheet, or a formatted worksheet created from an Excel template.
69
utlook is the Office component that is used for communicating via email, maintaining a calendar, and storing contact and task information. For email and appointments (a set of appointments in a folder is called a calendar), the Outlook interface is so superior that I recommend not trying to replicate its functionality in Access, but instead to export Access data to Outlook, creating email messages, appointments, or other Outlook items as needed. Way back in Access 2.0, I created a database to manage tasks, allowing me to assign them priorities, start and due dates, and notes, and order them by any of those priorities or dates. Of course, when Outlook was introduced in Office 97, my Tasks database was no longer needed, because Outlook includes its own Task List (or To Do List, as it is labeled in Office 2007). All the features I wanted were built in to the Outlook Task List, so I moved all my tasks to Outlook and managed them with Outlooks tools. Because Outlook does such a good job with tasks, there is no need to store task data in Access, though in some special circumstances you might need to do this, and then perhaps export the data to Outlook. Outlooks rarely used Journal component, which records the creation of selected Outlook items, as well as user-entered items, also has little need for connecting to Access. If you find this component useful (I have used it as part of my Time & Expense Billing application, to store time slip data), you can set up the Journal to record various types of Outlook items, and add manual entries to the Journal as needed. However (as with tasks), there may occasionally be circumstances in which you would need to export Access data to Outlook journal items, and I describe one of them later in this chapter.
IN THIS CHAPTER
Creating Outlook appointments and tasks from Access data Writing Access data to the Outlook Journal Creating emails to contacts in an Access table
71
Part I
If you store email addresses in a table of contacts, customers, or clients, you can use VBA code to create emails to them from an Access form, either to a single recipient or a group of recipients, without having to switch to Outlook. Contacts are another matter although Outlook has a Contacts component, with many useful features (especially the link to email), nevertheless, Outlook contacts are deficient in one very important feature when compared to Access: All Outlook data is stored in a flat-file MAPI table, so you cant set up one-to-many relationships between (for example) companies and contacts, or contacts and phone numbers. If a company moves to another location or changes its name, you have to make the change manually in each contact for that company; if a contact has more than three addresses, or a phone number that doesnt fit into one of the available categories, you are out of luck. For contacts, you really need both the attractive interface and built-in email connectivity of Outlook contacts, and the relational database capabilities of Access. This means you need a way to synchronize data between Outlook and Access contacts; my Synchronizing Contacts.accdb database does just this.
CROSS-REF
See Chapter 11 for a discussion of the Synchronizing Contacts database. Chapter 8 deals with exporting and importing contacts without synchronization.
This chapter concentrates on exporting tasks, appointments, and journal items from Access to Outlook and creating emails to contacts stored in an Access table.
NOTE
72
Check whether selected date is a weekend day, and put up error message and exit if so:
Case vbSunday, vbSaturday strTitle = Wrong day of week strPrompt = _ Reviews cant be scheduled on a weekend MsgBox strPrompt, vbOKOnly + vbExclamation, _ strTitle Cancel = True GoTo ErrorHandlerExit Case vbMonday, vbWednesday, vbFriday
Check whether selected date is the wrong day of the week, and put up error message and exit if so:
strTitle = Wrong day of week strPrompt = Reviews can only be scheduled on _ & a Tuesday or Thursday MsgBox strPrompt, vbOKOnly + vbExclamation, _
73
Part I
NOTE
To work with Outlook items in code, you need to set a reference to the Outlook object library (select Tools References in the Visual Basic window, as shown in Figure 4.2). To avoid creating multiple instances of Outlook, I like to use an error handler that will open a new instance of Outlook using CreateObject if the GetObject function fails because Outlook is not running.
74
Once a correct Tuesday or Thursday date has been selected or entered, clicking the Schedule Appointment button creates three Outlook items: an appointment for the employee, an appointment for the supervisor (the person the employee reports to), and a task for the supervisor. The buttons Click event procedure is listed as follows:
Private Sub cmdScheduleAppt_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim appOutlook As Outlook.Application strEmployeeName As String strSupervisorName As String appt As Outlook.AppointmentItem fldTopCalendar As Outlook.Folder fldContactCalendar As Outlook.Folder fldSupervisorCalendar As Outlook.Folder fldTasks As Outlook.Folder tsk As Outlook.TaskItem nms As Outlook.NameSpace
75
Part I
76
The code first attempts to set references to the supervisors and employees folders under the default Calendar folder. If there is no folder for the employee (or supervisor), it then creates a new folder for the employee or supervisor, using the Add method of the Calendar folders Folders collection. Next, the Items collection of the supervisors folder is used to create a new item of the default item type in that folder, and similarly for the employees folder. You can also create a new item using the CreateItem method of the Outlook Application object, but that creates the item in the default folder; if you want to create an item in a custom folder, you need to use the Add method instead.
NOTE
You cant use the Add method directly with an Outlook folder; this method works with collections, such as the Items collection or the Folders collection.
Finally, you will get a Done message (Figure 4.3) reporting on the appointments and task that have been scheduled. Figure 4.4 shows several employee and manager folders under the default Calendar folder, and a supervisor appointment in the daily calendar.
NOTE
If you dont see the employee and manager folders, you are probably in another view; switch to Folder view to see the calendar folders.
77
Part I
FIGURE 4.3 A success message with details about the Outlook items created.
FIGURE 4.4 Employee and supervisor folders and an appointment created from code.
78
The function that exports the mainframe data to Outlook journal items is listed as follows (for convenience, this function is run from the macro mcrExportTransactions):
Public Function ExportTransactions() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim appOutlook As Outlook.Application jnl As Outlook.JournalItem dbs As DAO.Database rst As DAO.Recordset strBody As String strPrompt As String strTitle As String
Set appOutlook = GetObject(, Outlook.Application) Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblMainframeData) Do While Not rst.EOF Set jnl = appOutlook.CreateItem(olJournalItem)
79
Part I
Create a text string with data from various table fields, for writing to the journal items Body field:
strBody = IIf(rst![Debit] > 0, Debit of _ & Format(rst![Debit], $###,##0.00) _ & for , ) & IIf(rst![Credit] > 0, _ Credit of & Format(rst![Debit], _ $###,##0.00) & for , ) _ & Account No. & rst![Account] Debug.Print Body string: & strBody jnl.Body = strBody jnl.Close (olSave) rst.MoveNext Loop strTitle = Done strPrompt = All transactions exported to Outlook _ & journal items MsgBox strPrompt, vbOKOnly + vbInformation, strTitle ErrorHandler: Outlook is not running; open Outlook with CreateObject If Err.Number = 429 Then Set appOutlook = CreateObject(Outlook.Application) Resume Next Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Function
NOTE
When Outlook 2007 is first installed, the Journal component is turned off; activate it in order to see the journal entries created by the preceding procedure.
This function first sets up a DAO recordset based on tblMainframeData and loops through it, creating a new journal item in the default Journal folder for each record in the table, and setting its properties from data in the tables fields. There is a success message when all the data has been exported. Figure 4.6 shows a journal item created from a transaction record. To avoid having to create a custom Journal form, the code writes the Dept data to the Companies (Company in the interface) field of a standard Journal item. Data from several fields is concatenated into a String variable, which is written to the Body field (the large textbox at the bottom of the Journal item).
80
FIGURE 4.6 A journal item created from a record in a table of mainframe transaction data.
81
Part I
Two buttons let you quickly select (or deselect) all the contacts; once you have selected the email recipients, and entered the message subject and body, you can click the Create Email Messages button to create the set of emails and open them for review before sending. A set of email messages is shown in Figure 4.8.
The code that creates the email messages (and also the code that selects or deselects ListBox items) is listed here:
Private Sub cmdMergetoEMailMulti_Click() On Error GoTo ErrorHandler Set lst = Me![lstSelectContacts]
82
83
Part I
Resume Next Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Sub Private Sub cmdSelectAll_Click() On Error GoTo ErrorHandler Set lst = Me![lstSelectContacts] lngListCount = Me![lstSelectContacts].ListCount For lngCount = 0 To lngListCount lst.Selected(lngCount) = True Next lngCount ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number & ; Description: _ & Err.Description Resume ErrorHandlerExit End Sub Private Sub cmdDeselectAll_Click() On Error GoTo ErrorHandler Set lst = Me![lstSelectContacts] lngListCount = Me![lstSelectContacts].ListCount For lngCount = 0 To lngListCount lst.Selected(lngCount) = False Next lngCount ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number & ; Description: _ & Err.Description Resume ErrorHandlerExit End Sub
84
NOTE
If you prefer to send the email messages automatically (without reviewing them), replace the .Display line in the code with .Send.
Summary
With the techniques presented in this chapter, you can create tasks, appointments, email messages, or journal items from data in Access tables, allowing you to use Access as a control center, while making use of Outlook items where they offer a superior interface, or are more widely accessible for users.
85
n older versions of Microsoft Office, there were two choices for working with data stored in Access tables. One was the Data Access Objects (DAO) object model, which was developed specifically to work with Access data in recordsets (including form recordsets) and to work with table structure using the Tables (and subsidiary Fields) collections. Because of these customized features, DAO was the best object model for working with Access data. The other choice for working with Access data was (and is) the ADO object model, introduced with Visual Studio 97, and available for use in Office 2000 and up. This object model is intended for working with data in a wide variety of sources, including Access databases. Although it lacks some of the customized features that make DAO so well suited to Access data, ADO code works fine for basic data manipulation, where you dont need to work with Access form recordsets or create tables and fields (in other words, you are just working with data in Access tables, not with their structure). Before the release of Office 2007, word was out that Microsoft was dropping support for the DAO object model (and indeed you can see statements to this effect in various online Microsoft documents). I wondered whether ADO would be updated to work with form recordsets, and the Tables (and subsidiary Fields) collection (or some alternate method for creating tables and their fields programmatically), because there are situations when you need these features of DAO, while working in an Access database, or creating an Access add-in.
IN THIS CHAPTER
Using the old and new DAO object models to work with Access data Working with Access databases in formats from 2000 to 2007 Using the ADO object model to work with Access data Converting DAO code to ADO code
89
Part II
NOTE
DAO library references older than 3.6 are not supported in Access 2007. This means that if you have references to older DAO versions in any older format databases you want to work with in Access 2007, you will need to reset these references to DAO 3.6. Databases that were created in Access 2000 format with a DAO 3.6 reference, when opened in Access 2007, still have a reference set to the DAO 3.6 object model (as shown in Figure 5.1), and their DAO code compiles and runs. The same is true of Access 2002/2003 format databases opened in Access 2007.
FIGURE 5.1 A reference to the DAO 3.6 object model in an Access 2000 database opened in Access 2007.
As it turned out, the DAO object model is still supported, though with some changes. If you convert an earlier format database to Access 2007 format, or create a new Access 2007 database, by default it has a reference to the Microsoft Office 2007 Access database engine object model (file name: ACCESS 2007 DAO.DLL). This new object model (which I will hereafter reference as Access 2007 DAO) has the same core functionality as DAO 3.6 (with a few differences, which are discussed in the New Objects in the Access 2007 DAO Object Model section), and the same object model abbreviation (DAO) for use in declarations, so Microsoft hasnt really pulled DAO after all instead, they renamed it, added a new object and a few new attributes, and hid some of the components that are not directly related to working with Access data. This means that all your old DAO code will run the same as before, so you dont need to convert it to ADO (though of course you can, if you want to use the more modern ADO object model), and if you are working with an Access 2007 database, you will have a few new object model components that represent the new features in Access 2007.
NOTE
The sample database for this chapter is DAO and ADO Sample Code.accdb. In addition, some of the code references the sample database Northwind 2007.accdb, which you can create by double-clicking the Northwind.accdt template in the C:\Program Files\ Microsoft Office\Templates\1033\Access folder.
90
FIGURE 5.2 The default Access 2007 DAO reference in a new Access 2007 database.
But since the introduction of the ADO object model, you may run into problems with such declarations, because certain object names are used in both of these object models. This is true of the
91
Part II
new Access 2007 DAO object model as well as the old DAO object model. If you compare the DAO (or Access 2007 DAO) and ADO object models, you will see Recordset, Parameter, and Field objects in both. When your code is compiled, if the declarations dont include the object model, the first reference in the list of references that contains that object name is used, and it may not be the right one. In Access 2000 and XP (perhaps prematurely), new databases had a default reference only to the ADO object model, which led to many problems for users and developers who were working primarily or exclusively with DAO (see Figure 5.3). However, if you create a new database in Access 2003, in the Access 2002/2003 database format, by default it will have references set to both the DAO and ADO object models, in that order, as shown in Figure 5.4.
FIGURE 5.3 The default references for a new Access 2002 (XP) database.
This means that all Recordset, Field, and Parameter variables declared without an object model reference will be interpreted as belonging to the DAO object model, which may not be correct. If your database was first created in Access 2000 or XP, and you didnt set a reference to the DAO object model, you will have the opposite problem Recordset, Field, and Parameter variables will be interpreted as belonging to the ADO object model, which could cause problems when working with DAO object properties and methods.
92
FIGURE 5.4 The default references for a new Access 2003 database.
TIP
When using one of the Find* search methods (FindFirst, FindNext, and so forth), save the search string to a variable, and display it in the Immediate window using a Debug.Print statement; this will be very helpful in debugging any problems because it shows you exactly what expression is being used for the search.
If you are working on a database originally created several Access versions ago, it might have ambiguous declarations such as:
Dim dbs As Database Dim rst As Recordset
As a demonstration of possible problems, the following procedure sets up a recordset and uses FindFirst to locate the first match for Microsoft in the CompanyID field:
Private Sub TestFindFirst() Dim dbs As Database Dim rst As Recordset Dim strSearch As String Set dbs = CurrentDb Set rst = dbs.OpenRecordset(Name:=tblCompanyIDs, _ Type:=dbOpenDynaset) strSearch = [CompanyID] = & Chr$(39) _ & Microsoft & Chr$(39) Debug.Print Search string: & strSearch rst.FindFirst strSearch End Sub
93
Part II
If you have a reference set only to the ADO object model, this code wont even compile, and you will get a User-defined type not defined error. If you have references set to both object models, and the ADO reference is first, you will get a different error, this time on the line with the FindFirst method reference: Method or data member not found. If you have just a DAO reference, or the DAO reference is positioned above the ADO reference, the code will compile and run.
TIP
If you get a User-defined type not defined or Method or data member not found error message when compiling code, this is almost always an indication of a missing or incorrect object model version reference.
A more subtle problem could result from an ambiguous declaration of the Field object. There is a Field object in both the ADO and DAO object models, but it has different properties and methods in each object model (see Figures 5.5 and 5.6), so a line of code referencing a Field property could lead to an error if that property is not supported in the object model that is being used.
NOTE
Although the Access 2007 interface is very different than the interface you might be used to, which has remained pretty much the same from Access 2000 through Access 2003, the Visual Basic (Modules) window is unchanged, except that the mouse wheel now works (about time!).
he Object Browser is a very useful tool for examining object models and their components. It can be opened from a Visual Basic window in Access, Word, Excel, or Outlook from a command on the View menu or by pressing the F2 key. The drop-down list at the top-left lists the available object libraries (corresponding to the references you have set in the database); the lower drop-down list is a search box where you can enter the name of an object model component or attribute to search for; clicking the binoculars button starts the search, and the results are displayed in the Search Results box, as in the following figure. The Classes list shows the members of the selected object library, and the Members of Field list shows the attributes (properties, methods, and events) of the selected object model component. If you click the yellow question mark button, you will usually get a Help topic for the selected object or attribute, but you cant depend on this sometimes all you get is a blank Help window. In the case of ADO, in previous versions of Access, if you set a reference to the most recent version of this library (2.8 at that time), you would get blank Help pages; if you set a reference to ADO 2.5, however, you would get the appropriate Help topic. In Access 2007, if you set a reference to the highest version of ADO (6.0), you will get an Unable to display Help error message on clicking the Help button. If you set a reference to ADO 2.5, clicking the Help button opens the Browse Access Developer Help screen, rather than the specific Help topic for the selected object model component or attribute. Figure 5.5 shows the attributes of the ADO Field object in the Object Browser.
94
FIGURE 5.5 The attributes of the Field object in the ADO object model.
There are many more properties for the Field object in the DAO object model (Figure 5.6), corresponding to specific Access field properties; the ADO Field properties are more generic, because ADO supports data in many different applications. The solution to the ambiguous reference problems discussed previously is simple: include the object model name in declarations of DAO and ADO variables, as in the following declarations for DAO variables (either DAO 3.6 or Access 2007 DAO). This is called disambiguating the declarations:
Dim rst as DAO.Recordset Dim fld as DAO.Field
Here is the ADO version (note that the object model name is not ADO, as you might think, but ADODB):
Dim rst as ADODB.Recordset Dim fld as ADODB.Field
95
Part II
FIGURE 5.6 The attributes of the Field object in the DAO object model.
I prefer to always include the object model name in declarations, even if the object in question only appears in one object model, for consistency and in case that object name might be used in some other object model I might need to reference in the future.
96
Workspaces Workspace Databases Database Containers Container Documents QueryDefs QueryDef Fields Parameters Recordsets Recordset Fields Relations Relation Fields TableDefs TableDef Fields Indexes Groups Group Users Users User Groups Group User Field Index Fields Field Field Field Field Parameter Document
ComplexType
The ComplexType object represents a multi-valued field and has its own Fields collection to hold the values.
Field2
This object represents a field in an Access 2007 database. It has the new attributes outlined in Table 5.1, as compared with the Field property, representing a field in an Access 2000 or Access 2002/2003 database.
97
Part II
TABLE 5.1
Property
AppendOnly
Gets or sets a Boolean value that indicates whether the specified field is set to append new values to the existing contents of the field as they are added (Read/Write). Represents a multi-valued field (Read-only). Returns a Boolean value that indicates whether the specified field is a multi-valued data type (Read-only). Loads the specified file from disk. Saves an attachment to disk.
Recordset2
The Recordset2 object represents a recordset based on Access 2007 data. Compared with the old Recordset object, it has only one new property, ParentRecordset, representing the parent Recordset of the specified recordset.
TABLE 5.2
Database Components Hidden in the Microsoft Office 2007 Access Database Engine Object Model
Collection or Object Properties Methods Constants
Container
DBEngine
SystemDB
98
Collection or Object
Properties
Methods
Constants
Group/Groups User/Users Workspace Groups UserName Users WorkspaceTypeEnum dbUseODBC CreateGroup CreateUser
Databases
When working with Access data, a DAO Database object represents (no surprise!) an Access database. (For working with other types of data, use the ADO object model.) To reference the current database, you can use the CurrentDb method of the Access Application object, after declaring the appropriate DAO Database variable, as in the following code:
Dim dbs as DAO.Database Set dbs = CurrentDb
If you need to reference an external database, use the OpenDatabase method, with the name of an open database as its argument, as in the following code:
Dim dbs As DAO.Database Dim strDBName As String strDBName = E:\Documents\Northwind.mdb Set dbs = OpenDatabase(Name:=strDBName)
Once the database is open, you can proceed to work with it, using the Recordsets, QueryDefs, and TableDefs collections.
Recordsets
Recordsets are used to manipulate data in Access databases; they represent the records in tables or queries in a database. There are five types of DAO recordsets, with different properties, as described in the following sections. Specify the recordset type by using the appropriate constant for the type argument when creating a recordset, as in the following code:
Set rst = dbs.OpenRecordset(Name:=tblOrders, _ Type:=dbOpenDynaset)
Table 5.3 lists the named constants corresponding to the five recordset types (and their numeric equivalents). These named constants are used in VBA code; some dialects of Visual Basic, such as
99
Part II
VBScript (VBS), dont support named constants, so you need to use the numeric values instead, for example when writing Outlook VBS code or Windows Script Host (WSH) code.
CROSS-REF
TABLE 5.3
If you dont specify a recordset type, DAO assumes first that you intend to create a table-type recordset, and if that is impossible, then a dynaset, then a snapshot, then a forward-only recordset. The table- and dynaset-type recordsets are the most commonly used types.
Table
Table-type recordsets represent base tables (that is, tables located within the database from which the code is running, as opposed to linked tables). You can add, edit, or delete records from a table using a table-type recordset. These recordsets dont support the Find methods (FindFirst, FindLast, FindNext, FindPrevious); instead they support the Seek method.
NOTE
To run one of the procedures in the DAO and ADO Sample Code database, place your cursor inside the procedure and press F5, or select Run Sub/UserForm from the Run menu in the Visual Basic window. If you dont get a response when pressing a function key, function keys may be disabled; some newer keyboards disable function keys by default. To enable function keys, press the F Lock key.
TIP
The following code segment searches for the record with a specific value (retrieved from an InputBox) in the CompanyID field and, if it is found, displays the value in the ID/AccountNumber field for that record in a message box:
Private Sub ListID() Dim dbs As DAO.Database Dim rst As DAO.Recordset
100
Dim strValue As String Dim strPrompt As String Dim strTitle As String Set dbs = CurrentDb Set rst = dbs.OpenRecordset(Name:=tblCompanyIDs, _ Type:=dbOpenTable) rst.Index = CompanyID EnterID: strValue = InputBox(prompt:=Please enter a company ID, _ Title:=Company ID, Default:=TEAC) rst.Seek Comparison:==, key1:=strValue If rst.NoMatch = True Then strPrompt = Couldnt find & strValue & _ ; please try again strTitle = Search failed MsgBox prompt:=strPrompt, Buttons:=vbCritical _ + vbOKOnly, Title:=strTitle GoTo EnterID Else strPrompt = The first ID for & strValue _ & is & rst![ID/AccountNumber] strTitle = Search succeeded MsgBox prompt:=strPrompt, Buttons:=vbOKOnly _ + vbInformation, Title:=strTitle End If End Sub
Figure 5.8 shows the message box with the ID for the selected company.
Dynaset
Dynaset-type recordsets represent the results of updatable queries, possibly based on more than one table. You can use these recordsets to add, edit, or delete records from one or more base tables. Dynaset-type recordsets support the more flexible Find methods (FindFirst, FindLast,
101
Part II
Argument Styles
hen writing VBA code, you have two style choices: using argument names (as I do in most of the procedures in this book), which is more verbose, but allows you to skip arguments without causing syntax errors; or omitting argument names, in which case you have to make sure you have the right number of commas between arguments, with the spaces between commas representing the arguments you arent using. I prefer using argument names for clarity, even though it makes my code a little longer. If you use one argument name, you must use argument names for all the arguments of a function or method you use mix and match is not permitted.
FindNext, FindPrevious); unlike the Seek method for table-type dynasets, you dont need to set an index, and you can search for a value in any field in the recordset. Here is an example in which the code searches for the last record with a matching value in the IDLabel field and displays the Company ID for that record in a message box: Private Sub ListCompany() Dim Dim Dim Dim Dim Dim dbs As DAO.Database rst As DAO.Recordset strValue As String strPrompt As String strTitle As String strSearch As String
EnterID: strValue = InputBox(prompt:=Please enter an ID label, _ Title:=ID Label, Default:=E-Mail Address) strSearch = [IDLabel] = & Chr$(39) & strValue _ & Chr$(39) Debug.Print Search string: & strSearch Set dbs = CurrentDb Set rst = dbs.OpenRecordset(Name:=tblCompanyIDs, _ Type:=dbOpenDynaset) rst.FindLast strSearch If rst.NoMatch = True Then strPrompt = Couldnt find & strValue & _ ; please try again strTitle = Search failed MsgBox prompt:=strPrompt, Buttons:=vbCritical _ + vbOKOnly, Title:=strTitle GoTo EnterID Else
102
strPrompt = The last Company ID for & strValue _ & is & rst![CompanyID] strTitle = Search succeeded MsgBox prompt:=strPrompt, Buttons:=vbOKOnly _ + vbInformation, Title:=strTitle End If End Sub
Snapshot
A snapshot-type recordset is a read-only copy of a set of records, useful only for viewing data or generating reports. The following procedure moves through a recordset based on a table, writing the values in several fields to the Immediate window, using the VB constants vbCrLf and vbTab to create line breaks and indents for better readability:
Private Sub ListValues() Dim dbs As DAO.Database Dim rst As DAO.Recordset Set dbs = CurrentDb Set rst = dbs.OpenRecordset(Name:=tblCompanyIDs, _ Type:=dbOpenSnapshot) Do While Not rst.EOF Debug.Print Company ID: & rst![CompanyID] _ & vbCrLf & vbTab & ID Label: & rst![IDLabel] _ & vbCrLf & vbTab & ID/Account No.: _ & rst![ID/AccountNumber] & vbCrLf rst.MoveNext Loop End Sub
The results of running this procedure for two records are listed as follows:
Company ID: MS Office & VBA ID Label: CIS ID ID/Account No.: 70304,3633 Company ID: Fisher Consulting ID Label: E-Mail Address ID/Account No.: [email protected]
Unlike table-type and dynaset-type recordsets, you can work with a snapshot-type recordset even if the underlying table is open, which can occasionally be useful.
103
Part II
Forward-only
Forward-only recordsets are similar to snapshot-type recordsets, except that you only move through the records in a forward direction.
Dynamic
Dynamic recordsets represent the results of updatable queries, possibly based on more than one table. You can use these recordsets to add, edit, or delete records from one or more base tables, and so can other users.
QueryDefs
QueryDefs correspond to queries in the Access interface. Though you can create dynaset-type recordsets based directly on queries, and for select queries that works fine, QueryDefs offer extra functionality: you can create a query on the fly, in VBA code (for example, to filter by a value entered or selected on a form), and then use that QueryDef as the data source for a recordset. You can even create a make-table query in code, and execute it to create a table, for circumstances where you need a table to work with. This eliminates the need for numerous filtered queries, and also lets you work around various problems with creating recordsets based on parameter queries. The following procedure creates a QueryDef programmatically, using a SQL string as the data source, and returns the number of records; it is useful in determining whether there are any records in a filtered query, before taking an action. I call this procedure frequently in code in the sample databases for this book:
Public Function CreateAndTestQuery(strTestQuery As String, _ strTestSQL As String) As Long On Error Resume Next
104
.MoveFirst .MoveLast CreateAndTestQuery = .RecordCount End With ErrorHandlerExit: Exit Function ErrorHandler: If Err.Number = 3021 Then CreateAndTestQuery = 0 Resume ErrorHandlerExit Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Function
The code creates a SQL string filtered by a value picked up from a form, and uses that string and a query name as arguments for the CreateAndTestQuery function. That function returns the number of records; if there are no records (the function returns zero), the code exits; otherwise, it can continue to perform some action on the query created by the CreateAndTestQuery function. You can also create a QueryDef and use it directly to create a recordset, as in the following line of code:
Set rst = qdf.OpenRecordset
105
Part II
NOTE
Argument names are inconsistently capitalized in VBA code. Regardless of how you type them in, when your cursor leaves the line of code, some argument names are capitalized (such as Name) and some are not (such as sqltext). The capitalization does not match the capitalization of the arguments in their Help topics, where they are usually represented as all lowercase.
You can also create a QueryDef corresponding to an action query and run it directly from code, to create a table for use elsewhere in the code instead of a parameter query, to avoid errors that will occur if a query criterion is looking for a value on a form that is closed when the code runs:
strFilter = [InvoiceDate] = # & dteDue & # strSQL = SELECT [InvoiceNo], InvoiceDate, _ Customer, Employee _ & INTO tmakMatchingRecords _ & FROM tblInvoices _ & WHERE & strFilter & ; Debug.Print SQL string: & strSQL Set qdf = dbs.CreateQueryDef(Name:=strQuery, _ sqltext:=strSQL) qdf.Execute
TIP
You can create a QueryDef without a name, using just double quotes, as in this line of code:
106
strDBName As String strDBNameAndPath As String strPrompt As String strTitle As String strTable As String strCurrentPath As String fso As New Scripting.FileSystemObject fil As Scripting.File
strCurrentPath = Application.CurrentProject.Path & \ strDBName = Northwind 2007.accdb strDBNameAndPath = strCurrentPath & strDBName
107
Part II
.Fields.Append fld Set fld = .CreateField(Name:=Department, _ Type:=dbText, Size:=14) .Fields.Append fld Set fld = .CreateField(Name:=Shift, _ Type:=dbText, Size:=20) .Fields.Append fld Set fld = .CreateField(Name:=AnnualBonus, _ Type:=dbCurrency) fld.DefaultValue = 500 .Fields.Append fld Set fld = .CreateField(Name:=ShiftSupervisor, _ Type:=dbBoolean) fld.DefaultValue = False .Fields.Append fld End With
List the TableDefs in the database after appending the new table.
Debug.Print TableDefs in & dbsNorthwind.Name For Each tdf In dbsNorthwind.TableDefs Debug.Print vbTab & tdf.Name Next tdf dbsNorthwind.Close ErrorHandlerExit: Exit Sub ErrorHandler: If Err.Number = 3010 Then strPrompt = Table name already used; _ & please enter another name strTitle = Duplicate table name MsgBox prompt:=strPrompt, _ Buttons:=vbExclamation + vbOKOnly, Title:=strTitle GoTo NameNewTable Else MsgBox Error No: & Err.Number & ; Description: _ & Err.Description Resume ErrorHandlerExit End If End Sub
108
Here is the list of tables as printed to the Immediate window by the NewTable procedure:
MSysAccessStorage MSysACEs MSysComplexColumns MSysNavPaneGroupCategories MSysNavPaneGroups MSysNavPaneGroupToObjects MSysNavPaneObjectIDs MSysObjects MSysQueries MSysRelationships Order Details Orders Products Shippers Suppliers tblNew
NOTE
If you open tblNew in datasheet view, you will see a zero in the ShiftSupervisor Boolean field. If you prefer to see True/False or Yes/No values, you will have to select the format of your choice manually; the CreateField method does not have an argument for setting the display format for a field.
n addition to the CreateTableDef method, there are three other ways to create Access tables in VBA code:
n The CopyObject method of the DoCmd object in the Access object model creates a new
table based on an existing table.
n Executing a make-table query using the OpenQuery or RunSQL method of the DoCmd
object, or the Execute method of QueryDef object, creates a new table as the output of the query.
n A Jet SQL CREATE TABLE statement, as in the following code, can be used to create a
table:
strSQL = CREATE TABLE & tblForms & _ (FormName TEXT (100), Use YESNO); DoCmd.RunSQL strSQL
Compared with these techniques, the CreateTableDef method gives you the maximum control over the new tables fields and their properties. However you create a table, it will be appended to the TableDefs collection, and it will show up in the Tables group of the database window.
109
Part II
Connection
Although the ADO object model is not hierarchical (unlike the DAO object model), the Connection object is the foundation object, because connections are the link to data in databases.
NOTE
In DAO recordsets, the BOF property represents the beginning of the recordset (Beginning Of File), and the EOF (End Of File) property represents the end.
The handy NoMatch property of DAO recordsets is missing from ADO; instead, you have to determine whether or not a search succeeded by examining where the cursor is. For example, in doing a Find, starting from the beginning of the recordset (BOF) and moving forward, if the cursor ends up at the end of the recordset (EOF), the search was unsuccessful. Here is some sample code to illustrate this technique; if the search is successful, this means that the proposed new category name has already been used, whereas if the cursor ends up at the end of the recordset (EOF), the search was unsuccessful, and the new record can be created using the new category name in the strSearch variable (the code segment is part of the TestKeysetOptimistic procedure, which is listed later in this chapter):
rst.Find strSearch If rst.EOF = False Then strPrompt = Chr$(39) & strCategory _ & Chr$(39) & already used; _ & please enter another category _ & name strTitle = Category used MsgBox prompt:=strPrompt, _ Buttons:=vbExclamation + vbOKOnly, _ Title:=strTitle GoTo CategoryName
The Tables collection and form recordsets are not supported in ADO, so you will need to continue to use DAO (either the old DAO 3.6 or the new Access 2007 DAO object model) to work with them.
TIP
If you dont close and set to Nothing DAO database or recordset objects, it is extremely unlikely that you will have any problems; however, if you leave ADO connections and recordsets open, the next time you run the code, you may get this error message (with your logon name and computer name), and you will have to close down and reopen the database to get the code working again: The database has been placed in a state by user Admin on machine DELL_DIMEN_8300 that prevents it from being opened or locked. The sample ADO procedures have code to close any open connection or recordset in their error handlers.
110
The ADO syntax for working with a recordset in an external database is a little different; you have to specify the database path and file name, and specify the Microsoft Jet 4.0 provider, as in the procedure listed as follows, which uses a SQL string to create a recordset:
Private Sub OpenRecordsetSQL() On Error Resume Next Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim cnn As ADODB.Connection rst As ADODB.Recordset strDBName As String strConnectString As String strSQL As String strDBNameAndPath As String strCurrentPath As String fso As New Scripting.FileSystemObject fil As Scripting.File strPrompt As String
111
Part II
Need to specify the Jet 4.0 provider for connecting to Access .mdb format databases.
With cnn .Provider = Microsoft.Jet.OLEDB.4.0 .Open strDBNameAndPath strConnectString = .ConnectionString End With
Iterate through the recordset, and print values from fields to the Immediate window.
With rst .MoveLast .MoveFirst Debug.Print .RecordCount _ & records in recordset & vbCrLf Do While Not .EOF Debug.Print Australian Company name: _ & ![CompanyName] _ & vbCrLf & vbTab & Contact name: _ & ![ContactName] _ & vbCrLf & vbTab & City: & ![City] _ & vbCrLf rst.MoveNext Loop End With ErrorHandlerExit:
112
If Not cnn Is Nothing Then If cnn.State = adStateOpen Then cnn.Close Set cnn = Nothing End If End If Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
Command
The ADO Command object represents SQL commands, roughly equivalent to queries in Access databases, or QueryDefs in the DAO object model. You dont need to use this object to query or filter Access data; this can be done using a SQL statement (as in the preceding code segment) or the name of a saved query for the Source argument when opening a recordset. However, the Command object can be useful when you want to reuse a command later in the code, or if you need to pass detailed parameter information with the command. The procedure uses a Command object to create a recordset, which can be used later in the code:
Private Sub OpenRecordsetCommand() On Error Resume Next Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim cnn As ADODB.Connection rst As ADODB.Recordset cmdSQL As ADODB.Command strDBName As String strConnectString As String strSQL As String strCursorType As String strLockType As String strDBNameAndPath As String strCurrentPath As String fso As New Scripting.FileSystemObject fil As Scripting.File strPrompt As String
113
Part II
Need to specify the Jet 4.0 provider for connecting to Access .mdb format databases.
With cnn .Provider = Microsoft.Jet.OLEDB.4.0 .Open strDBNameAndPath strConnectString = .ConnectionString End With Set cmdSQL = New ADODB.Command Set cmdSQL.ActiveConnection = cnn
114
strLockType = Switch(rst.LockType = _ adLockOptimistic, _ Optimistic ( & adLockOptimistic & ), _ rst.LockType = adLockReadOnly, Read-only ( _ & adLockReadOnly & ), _ rst.LockType = adLockBatchOptimistic, _ BatchOptimistic ( _ & adLockBatchOptimistic & ), _ rst.LockType = adLockPessimistic, _ Pessimistic ( _ & adLockPessimistic & )) Debug.Print Recordset cursor/lock type: _ & strCursorType & , & strLockType & vbCrLf
Iterate through the recordset, and print values from fields to the Immediate window.
With rst .MoveFirst Do While Not .EOF Debug.Print Swedish Company name: _ & ![CompanyName] _ & vbCrLf & vbTab & Contact name: _ & ![ContactName] _ & vbCrLf & vbTab & City: & ![City] _ & vbCrLf rst.MoveNext Loop End With ErrorHandlerExit:
115
Part II
ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
Recordset
ADO recordsets represent sets of records in a database, much like DAO recordsets, though their attributes are more generic. An ADO recordset can be based on a table, query, SQL statement, or Command object. The TestForwardReadOnly procedure listed here uses a saved select query as the recordset source:
Private Sub TestForwardReadOnly() On Error GoTo ErrorHandler Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset
Iterate through the query, and print values from fields to the Immediate window.
Do While Not rst.EOF Debug.Print Company ID: & rst![CompanyID] _ & vbCrLf & vbTab & Category: _ & rst![Category] _ & vbCrLf & vbTab & Company Name: _ & rst![Company] & vbCrLf rst.MoveNext Loop ErrorHandlerExit:
116
ADO recordsets have four types of cursors (cursors in ADO are roughly equivalent to the DAO recordset types), as described in the following two tables. Each cursor type supports different methods depending on the setting of the LockType argument. The two most commonly used lock types are the read-only (named constant: adLockReadOnly) and optimistic (named constant: adLockOptimistic) type. Table 5.4 lists the ADO cursor types, with their numeric equivalents, and Table 5.5 lists the most commonly used cursor and lock type combinations for working with Access data.
TABLE 5.4
2 1 3 0
117
Part II
TABLE 5.5
1 3 4 2
NOTE
Sometimes the ADO recordset that is created is not the type you specify in the CursorType argument, depending on the lock type. In particular, if you specify the adLockOptimistic lock type for any cursor type, you will actually get a Keyset cursor. To determine the actual recordset type, use the following statement (see Table 5.4 to convert the numeric type to its matching named constant):
Private Sub TestMethodsSupported() On Error Resume Next Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim cnn As ADODB.Connection rst As ADODB.Recordset strDBName As String strConnectString As String strSQL As String strCursorType As String strLockType As String strCurrentPath As String fso As New Scripting.FileSystemObject fil As Scripting.File strDBNameAndPath As String strPrompt As String
118
Set fil = fso.GetFile(strDBNameAndPath) If fil Is Nothing Then strPrompt = Cant find & strDBName & in _ & strCurrentPath & ; please copy it from the _ & Office11\Samples subfolder under the main _ & Microsoft Office folder _ & of an earlier version of Office MsgBox strPrompt, vbCritical + vbOKOnly GoTo ErrorHandlerExit End If On Error GoTo ErrorHandler Set cnn = New ADODB.Connection Set rst = New ADODB.Recordset
Need to specify the Jet 4.0 provider for connecting to Access databases.
With cnn .Provider = Microsoft.Jet.OLEDB.4.0 .Open strDBNameAndPath strConnectString = .ConnectionString End With
Use a SQL string to create a filtered recordset.
strSQL = SELECT CompanyName, ContactName, City _ & FROM Suppliers _ & WHERE Country = Australia _ & ORDER BY CompanyName;
Modify the cursortype and locktype arguments as desired to test what type of recordset is created when the procedure is run.
rst.Open Source:=strSQL, _ ActiveConnection:=strConnectString, _ CursorType:=adOpenForwardOnly, _ LockType:=adLockOptimistic strCursorType = Switch(rst.CursorType = _ adOpenDynamic, _ Dynamic ( & adOpenDynamic & ), _ rst.CursorType = adOpenForwardOnly, _ Forward-only ( _ & adOpenForwardOnly & ), _ rst.CursorType = adOpenKeyset, Keyset ( _ & adOpenKeyset & ), _
119
Part II
rst.CursorType = adOpenStatic, Static ( _ & adOpenStatic & )) strLockType = Switch(rst.LockType = _ adLockOptimistic, _ Optimistic ( & adLockOptimistic & ), _ rst.LockType = adLockReadOnly, Read-only ( _ & adLockReadOnly & ), _ rst.LockType = adLockBatchOptimistic, _ BatchOptimistic ( _ & adLockBatchOptimistic & ), _ rst.LockType = adLockPessimistic, _ Pessimistic ( _ & adLockPessimistic & )) Debug.Print Recordset cursor/lock type: _ & strCursorType & , & strLockType & vbCrLf Debug.Print AddNew supported? _ & rst.Supports(adAddNew) Debug.Print Delete supported? _ & rst.Supports(adDelete) Debug.Print Find supported? _ & rst.Supports(adFind) Debug.Print MovePrevious supported? _ & rst.Supports(adMovePrevious) Debug.Print Update supported? _ & rst.Supports(adUpdate)
ErrorHandlerExit:
Close the Recordset and Connection objects.
If Not rst Is Nothing Then If rst.State = adStateOpen Then rst.Close Set rst = Nothing End If End If If Not cnn Is Nothing Then If cnn.State = adStateOpen Then cnn.Close Set cnn = Nothing End If End If Exit Sub
120
ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
Because other types of cursors are converted to keysets when you use optimistic locking for an ADO recordset, you may as well specify the keyset cursor when you create the recordset, because that is what you are going to get. See Table 5.6 for the details.
TABLE 5.6
AdLockOptimistic
AddNew Delete Find MoveFirst MovePrevious MoveNext MoveLast Update Find MoveFirst MovePrevious MoveNext MoveLast AddNew Delete Find MoveFirst MovePrevious MoveNext MoveLast Update Find MoveFirst MovePrevious MoveNext MoveLast
adLockReadOnly
adOpenKeyset
AdLockOptimistic
adOpenKeyset
AdLockReadOnly
continued
121
Part II
TABLE 5.6
(continued)
Lock Type Named Constant Available Methods
AdLockOptimistic
AddNew Delete Find MoveFirst MovePrevious MoveNext MoveLast Update Find MoveFirst MovePrevious MoveNext MoveLast AddNew Delete Find MoveFirst MovePrevious MoveNext MoveLast Update Find MoveFirst MoveNext MoveLast
adOpenStatic
AdLockReadOnly
AdLockOptimistic
adOpenForwardOnly
AdLockReadOnly
Dynamic
A dynamic cursor (DAO equivalent: dbOpenDynaset) lets you view additions, changes, or deletions made by other users. All types of movement through the recordset are allowed.
Keyset
In a recordset with a keyset cursor (there is no equivalent DAO recordset type), you can add, change, and delete data in records, but you cant see records that other users add or delete. However, you can see changes made by other users. With an optimistic (adLockOptimistic) lock type, you can modify the data; if you dont need to modify the data, use a read-only lock type (adLockReadOnly) for faster data access. The following TestKeysetOptimistic procedure adds a new record to the tlkpCategories table, and sets the value of a field from input provided by the user, after checking whether the category name provided by the user has already been used:
122
Private Sub TestKeysetOptimistic() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim cnn As ADODB.Connection rst As ADODB.Recordset strCategory As String strPrompt As String strTitle As String strSearch As String
Check whether this category name has already been used if the search fails, the cursor will be at the end of the recordset.
rst.Find strSearch If rst.EOF = False Then strPrompt = Chr$(39) & strCategory _
123
Part II
& Chr$(39) & already used; _ & please enter another category _ & name strTitle = Category used MsgBox prompt:=strPrompt, _ Buttons:=vbExclamation + vbOKOnly, _ Title:=strTitle GoTo CategoryName Else .AddNew ![Category] = strCategory .Update strPrompt = Chr$(39) & strCategory _ & Chr$(39) & added to table strTitle = Category added MsgBox prompt:=strPrompt, _ Buttons:=vbInformation + vbOKOnly, _ Title:=strTitle Debug.Print .RecordCount _ & records in recordset after adding End If End With End If ErrorHandlerExit:
124
The code prints the search string (always useful for debugging) and the number of records in the recordset, before and after adding the new record, to the Immediate window:
Search string: [Category] = Firmware 29 records initially in recordset 30 records in recordset after adding
Static
The static cursor type (DAO equivalent: dbOpenSnapshot) provides a static copy of a set of records, for viewing or printing data. All types of movement through the recordset are allowed. Additions, changes, or deletions made by other users are not shown. For fast access to data that you dont need to modify, where you dont need to view other users changes and you do need to be able to move both forward and backward in the recordset, use a static cursor and the adLockReadOnly lock type, as in the following TestStaticReadOnly procedure. If you do need to modify the data, but dont need to see other users changes, use the adLockOptimistic lock type instead (the cursor type will change to keyset, as noted previously). The TestStaticReadOnly procedure sets up a connection to the Northwind database, opens a filtered recordset based on a table in the database, and then iterates through the recordset, printing information from its fields to the Immediate window. Note that once an ADO recordset has been created, many of the same methods can be used to work with it as for a DAO database (BOF, EOF, Find*, Move*):
Private Sub TestStaticReadOnly() On Error Resume Next Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim cnn As ADODB.Connection rst As ADODB.Recordset strDBName As String strDBNameAndPath As String strConnectString As String strSQL As String strCurrentPath As String fso As New Scripting.FileSystemObject fil As Scripting.File strPrompt As String
125
Part II
Need to specify the Jet 4.0 provider for connecting to Access databases.
With cnn .Provider = Microsoft.Jet.OLEDB.4.0 .Open strDBNameAndPath strConnectString = .ConnectionString End With
Iterate through the recordset, and print values from fields to the Immediate window.
With rst .MoveLast .MoveFirst Debug.Print .RecordCount _ & records in recordset & vbCrLf Do While Not .EOF Debug.Print Australian Company name: _ & ![CompanyName] _ & vbCrLf & vbTab & Contact name: _ & ![ContactName] _
126
& vbCrLf & vbTab & City: & ![City] _ & vbCrLf rst.MoveNext Loop End With ErrorHandlerExit:
Forward-only
The forward-only cursor (DAO equivalent: dbOpenForwardOnly) allows only forward movement through a recordset and doesnt show additions, changes, or deletions made by other users.
127
Part II
It is the default cursor type. For the fastest access to data that you dont need to modify, use a forward-only cursor and the adLockReadOnly lock type, as in the TestForwardReadOnly procedure that follows; if you do need to modify the data, use the adLockOptimistic lock type instead:
Private Sub TestForwardReadOnly() On Error GoTo ErrorHandler Dim cnn As ADODB.Connection Dim rst As ADODB.Recordset
Iterate through the query, and print values from its fields to the Immediate window.
Do While Not rst.EOF Debug.Print Company ID: & rst![CompanyID] _ & vbCrLf & vbTab & Category: _ & rst![Category] _ & vbCrLf & vbTab & Company Name: _ & rst![Company] & vbCrLf rst.MoveNext Loop ErrorHandlerExit:
128
Set cnn = Nothing End If End If Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
Data from each record is printed to the Immediate window; the last two records data is listed here:
Company ID: Yclept Yarbro Category: Books Company Name: Yclept Yarbro Company ID: ZDExpos Category: Computer Company Name: ZDExpos
Record
An ADO Record object represents a set of data, which may be from a recordset or a non-database source. When working with Access data, the Record object is a single row from a recordset, or a one-row recordset. There are many specialized uses of Record objects based on non-Access data (in particular, for working with hierarchical data and displaying it in TreeView controls), but when working with Access data in VBA code there is no reason to use the Record object, because you can reference fields as needed on the current record in a recordset without creating a Record object.
Stream
A Stream object represents a stream of data from a text file, XML document, or web page. Because this object doesnt work with Access data, it is dealt with in the chapters on working with text files, specifically Chapters 9 and 17.
129
Part II
TIP
TABLE 5.7
You cant exchange data between ADO and DAO recordsets, even when working in databases with references set to both object models.
DBEngine Workspace Database Recordset Dynaset type Snapshot type Table type Field QueryDef TableDef
No equivalent No equivalent Connection Recordset Keyset cursor Static cursor Keyset cursor with acCmdTableDirect option Field No direct equivalent, but can use the Command object to get the same functionality No equivalent
When using the DAO object model to work with Access data, the following code segment opens a recordset based on a query in an external database:
Dim dbs as DAO.Database Dim strDBName As String Dim rst As DAO.Recordset strDBName = E:\Documents\Northwind.mdb Set dbs = OpenDatabase(Name:=strDBName) Set rst = dbs.OpenRecordset(Name:=qryCurrentOrders, _ Type:=dbOpenDynaset)
130
Need to specify the Jet 4.0 provider for connecting to Access databases.
With cnn .Provider = Microsoft.Jet.OLEDB.4.0 .Open strDBName strConnectString = .ConnectionString End With
Once the recordset has been created, you can work with it much like a DAO recordset, though there are some differences see the sections on ADO recordset cursor and lock types for details on the differences.
TIP
For further information on converting DAO code to ADO code see Alyssa Henrys article Porting DAO Code to ADO with the Microsoft Jet Provider, which is available online in the MSDN Library by searching its title or at http://msdn.microsoft.com/library/ default.asp?url=/library/en-us/dndao/html/daotoado.asp.
Summary
The DAO object model was developed to work with Access data, and (despite rumors of its death, which have been heard for many versions now, and heavy promotion of the alternative ADO object model) DAO is still the best object model for working with data in Access tables. In Access 2007 instead of removing the DAO object model, Microsoft wisely chose to trim it of some rarely used components and rename it the Microsoft Office 2007 Access database engine object model. My recommendation is to use DAO for all tasks involving Access data. When you need to work with data in other types of databases or data sources, however, ADO is the object model you need to use (no choice there DAO only works with Access data). ADO can be used to work with Access data as well, though it has some limitations compared to DAO, so in some cases you may want (or need) to use ADO to work with Access data; I have provided information on converting DAO code to ADO for these situations.
131
espite the new and improved report interactive features discussed in the Report Layout View sidebar, for full control over the appearance and content of documents filled with Access data, VBA Automation code working with Word documents is the best choice. This chapter discusses producing Word documents by using a Ribbon command to create simple documents, or writing VBA Automation code to create Word documents and fill them with data, using four different methods. In contrast to Access reports (even in the new Layout view), Word documents have extensive formatting options, including tables, form fields, and other specialized controls not available in Access reports, even in Layout view. Generating Word documents from Access VBA code lets you use all of Words formatting options and (if desired) to create a separate document for each Access record, instead of a multi-page mail merge document. And the Word documents containing merged Access data can be edited, which is not an option even for Access 2007 reports.
IN THIS CHAPTER
Office 2007 built-in Word export Components of the Word object model used in Automation code Creating Word documents filled with Access data, using four different methods
133
Part II
134
However, even an interactive report still falls far short of the functionality of a Word document, especially if you need to distribute documents containing Access data to people who dont have Access installed.
NOTE
You will see different selections on the More menu (or selections appearing as enabled or disabled) according to the type of object selected in the Object Bar, and whether the object is open or closed. With a form open, for example, you will see the Access Database, XML File, and HTML Document selections (these selections are enabled) and a disabled selection, Merge It with Microsoft Office Word; the Snapshot Viewer selection is only enabled when a report is selected.
FIGURE 6.1 The new External Data tab on the Access 2007 Ribbon, with the More menu dropped down.
135
Part II
The Word (RTF) and Word Mail Merge features in Access 2007 work much the same as in earlier Office versions. If you select an Access table, query, or other object, and then select the Word (RTF) option, all the data from the entire selected object is automatically exported to a new Word document, with no option for selecting records. The RTF document created from a table, query, or form is a Word table, which is a good match for data in an Access table or select query, but a very poor match for a form. Reports are created as text documents, not tables (even if they are tabular reports), with footers as text in the body of the document, and without most of their formatting; such a document is barely usable. (This is unchanged for many Office versions now.) The RTF export option may be useful for creating a quick-and-dirty Word document you can send to someone who doesnt have Access, but it is not useful for creating letters or other formatted Word documents. Figure 6.2 shows an Access table to be exported, and Figure 6.3 shows the Export dialog, with two options enabled and one disabled (because the object being exported is a table, there is no formatting to export). Figure 6.4 shows the Word table created by the RTF export. It has basically the same appearance as the Access table, but it lacks the alternate-row shading, even though Word 2007 supports this feature.
136
FIGURE 6.3 The Word RTF Export dialog when exporting an Access table.
FIGURE 6.4 A Word document created by exporting an Access table, using the Word (RTF) option.
137
Part II
The Word Mail Merge option runs a wizard, which is generally similar to the one in the last two Office versions. It offers you a choice of deselecting some records from the data source before performing the merge, and you can create a new merge letter on the fly, so this interface choice can be useful when you need to create a set of minimally formatted Word letters to recipients from an Access table or query but it may not be any easier to go through the six steps of the wizard, compared with just creating a simple Access letter report based on a filtered query. My conclusion, after reviewing the new data export features in Access 2007, is that (just as with previous versions of Access) if you want to be able to select the records for an export of Access data to Word, and to produce great looking documents that can be opened and possibly edited by all Office users, youre still best off writing VBA code to merge Access data to Word documents.
NOTE
The Word Export.accdb sample database contains the tables, queries, forms, and code used in this chapter.
138
ErrorHandler: If Err = 429 Then Word is not running; open Word with CreateObject Set appWord = CreateObject(Class:=Word.Application) Resume Next Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If
NOTE
In the error handler in the preceding code segment, the appWord Application object variable is set to Nothing. If you want to close down Word when your code has run, this is appropriate; but if you want to preserve the Word Application object for future use (generally a good idea), either comment out this line or delete it, as I have done in most of the procedures in the Word Export sample database.
To work with objects in an object model, first you need to set up a reference to the Application object, or some other object that can be used with the CreateObject or GetObject function. Although you use CreateObject or GetObject to set a reference directly to a Word Document object, generally it is best to create (or set a reference to) a Word Application object, and then use the appWord variable to get at other Word objects below the Application object, because many of the properties and methods you need to use in Automation code belong to the Application object, and you can access all the other objects through the Application object..
139
Part II
FIGURE 6.5 Examining the Word MailMerge object in the Object Browser.
140
ErrorHandler: If Err = 429 Then Word is not running; open Word with CreateObject Set appWord = CreateObject(Class:=Word.Application) Resume Next Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Function
FIGURE 6.6 The main menu of the Word Export sample database.
If you click the Contact Templates Path or Contact Documents Path button, a Browse dialog opens, where you can select the folder for storing templates or documents (see Figure 6.7).
CROSS-REF
The procedures that pop up these Browse dialogs are discussed in Chapter 9.
FIGURE 6.7
141
Part II
The NewDocFromTemplate function listed next creates a new document based on a template in the Contact Templates folder, using Word document properties to hold the data from Access. The document properties method is the most common technique I use for creating documents to fill with Access data. In the NewDocFromTemplate procedure, after creating the new document, the names of its document properties are printed to the Immediate window.
NOTE
As with previous versions of Word, although there is a CustomProperties collection in the Word 2007 object model, and a CustomProperty object, this collection and object actually belong to the Smart Tags, so if you declare variables of these data types, you will get a compile error; therefore, they must be declared as Object.
Public Function NewDocFromTemplate() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim appWord As Word.Application docs As Word.Documents strLetter As String strTemplateDir As String doc As Word.Document
142
Must declare this variable as Object because declaring it as Word.CustomProperties doesnt work:
Dim prps As Object
Must declare this variable as Object because declaring it as Word.CustomProperty doesnt work:
Dim prp As Object Set appWord = GetObject(Class:=Word.Application) strTemplateDir = GetContactsTemplatesPath() Debug.Print Templates directory: & strTemplateDir strLetter = strTemplateDir & DocProps.dot Debug.Print Letter: & strLetter Set docs = appWord.Documents docs.Add strLetter Set doc = appWord.ActiveDocument Set prps = doc.CustomDocumentProperties For Each prp In prps Debug.Print Property name: & prp.Name Next prp ErrorHandlerExit: Exit Function ErrorHandler: If Err = 429 Then Word is not running; open Word with CreateObject Set appWord = CreateObject(Class:=Word.Application) Resume Next Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Function
143
Part II
some fields lack data. I also create a separate ZipCode field for use in creating U.S. PostNet bar codes on envelopes or labels. In the sample database, this query is qryContactsForMerge. The calculated field expressions I used to concatenate data from the simple flat-file tblContacts are listed next. Depending on the fields in your table(s), these expressions will need to be customized for example, to deal with multi-field addresses or name prefixes and suffixes:
ContactName: [FirstName] & & [LastName] NameTitleCompany: [FirstName] & & [LastName] & Chr(13) & Chr(10) & [JobTitleCompany] JobTitleCompany: IIf(Nz([JobTitle])= And Nz([CompanyName])=,,IIf(Nz([JobTitle])<>,[JobTitle] & IIf(Nz([CompanyName])<>,Chr(13) & Chr(10) & [CompanyName]),[CompanyName])) CityStateZip: [City] & , & [StateOrProvince] & & [PostalCode]
WholeAddress: [StreetAddress] & Chr(13) & Chr(10) & [CityStateZip] & IIf(Nz([Country])<> And Nz([Country])<>USA, Chr(13) & Chr(10) & [Country],) ZipCode: IIf([Country]=USA Or Nz([Country])=,[PostalCode],) LastNameFirst [LastName] & IIf([FirstName],, & [FirstName],)
TIP
In VBA code, you can use the VB named constant vbCrLf to indicate a CR + LF (carriage return plus linefeed) to start a new line in a text string, but named constants cant be used in query field expressions, so I use the Chr(13) & Chr(10) syntax instead, using the numeric values of the CR and LF characters.
Using a query to do the concatenating (rather than creating expressions in VBA code) makes it much easier to verify that the expressions are returning the correct data, and to fix any problems before doing the merge. After creating the expressions, just switch to datasheet view to inspect the results, and then switch back to design view to fix any problems you see.
144
TABLE 6.1
Bookmarks
There is no need to open the properties sheet; bookmarks are inserted directly into the template. Bookmarks are more familiar to Word users than document properties. Creates a separate document for each record, which allows easy customization of specific documents. There is no link to the Access database, so documents can be opened even on another computer.
You cant insert the same bookmark twice in a template; to display the same information in two or more places, you either need to create another bookmark or use a cross-reference field that references the first bookmark. Users may inadvertently type into the text inside a bookmark, overwriting the exported value.
Document Properties
Data from a document property can be displayed in multiple locations, using fields. Creates a separate document for each record, which allows easy customization of specific records. There is no link to the Access database, so documents can be opened even on another computer.
Requires creating document properties in the template, in the custom tab of the properties sheet.
continued
145
Part II
TABLE 6.1
Method
(continued)
Advantages Disadvantages
TypeText
No advance preparation of any kind is needed; this method works with a document created from the default Word template, or a default labels document. There is no link to the Access database, so documents can be opened even on another computer.
Suitable only for very simple documents, such as mailing labels or tabular lists.
Mail Merge
Suitable for merging very large numbers of records, too large to create an individual document for each record.
Customization of individual records is difficult, because all data is merged to a single document. Creating a mail merge labels document is more complex than creating a labels document for use with the TypeText method.
NOTE
You can work with Word 97/2003 documents in Word 2007, as well as create new documents in the new Word 2007 format, so you dont need to redo all your templates just to get them to work in Office 2007. Some of the templates used for Word merge in the sample Word Export database are in Word 2007 format, and others are in Word 97/2003 format. The extensions differ for these two formats; new documents have the .docx extension, and new templates have the .dotx extension, whereas older ones have the .doc or .dot extensions, as shown in Figure 6.8. When you open a document or template in the older format, the title bar says (Compatibility Mode) after the file name. The new Type column in the Windows Vista Explorer shows the contents of a Word 2007 documents Keywords field, so you can use this built-in Word property to display relevant information in the Explorer.
TIP
146
FIGURE 6.8 Word templates and documents in Word 2002/2003 and Word 2007 formats.
FIGURE 6.9 The Properties selection on the new Word Prepare menu.
147
Part II
NEW FEATURE
The Properties command on the Office menu opens a new feature of Word 2007, the Document Information Panel (see Figure 6.10), where you can modify a few of the more common built-in document properties.
Next, click the drop-down Properties button in the title bar (the initial selection is Standard) and select Advanced Properties. At last, the familiar Word properties sheet opens, to the General tab (see Figure 6.11).
148
Click the Custom tab to see the custom document properties; these are the ones that are most commonly filled with data from Access fields. Figure 6.12 shows the Custom tab of a Word 2007 template properties sheet, with several custom document properties that are useful for creating letters and other documents filled with data from an Access select query.
You may also want to use some of the fields on the Summary tab (see Figure 6.13), in particular the Keywords field, which is displayed in the Type column in the Windows Vista Explorer. To create a new Word document property, enter its name in the Name field (no spaces, and you should avoid using the same name as a built-in property, although Word allows this), select the data type (Text, Numeric, Date, or Yes/No), enter a default value if desired, and click the Add button. Over years of working with Word document properties, I have discovered some limitations of Word document properties and developed some workarounds to deal with them: n If you dont specify a default value for a Text property, Word wont let you save it; instead, use a space (which is permitted) as the default value. n Date fields should generally be avoided, except for the rare cases where you actually need a default date value, because there is no way to give them a blank default value. You can format a Text value as a date, using Word field switches.
149
Part II
n Numeric fields should also be avoided, both because you cant make them blank (you may not want a zero appearing in your document when the field has no data from Access), and, more importantly, because all numbers are truncated to integers. A value of 49.21 in Access will be truncated to 49 in the Word document property. As with Date values, its best to save numeric values to a Text document property (Text values are not truncated), and then format them with the appropriate numeric format in Word. n Yes/No properties require you to select either Yes or No as the default value; if that is unacceptable, use a Text field, possibly converting the True or False values in an Access Yes/No field to Yes, No, or a zero-length string ().
TIP
Sometimes, in Word 2007, after delving down a few levels from the new Ribbon, you will see a familiar Word 2003 dialog box. If you see a tiny diagonal arrow in the lowerright corner of a group on a Ribbon, click the arrow, then the image of the dialog, to open the familiar Word 2003 dialog box for that feature (see Figure 6.14).
150
FIGURE 6.14 Opening the old Paragraph dialog box from the new Ribbon.
FIGURE 6.15 A form for browsing contacts, with a button for creating a Word letter.
151
Part II
hen you use Word document properties to merge Access data to Word documents, the values written to the document properties are displayed on the Word document in DocProperty fields. You can use field switches to format the data displayed in the DocProperty field in a variety of ways, which is going to be necessary if you follow my advice and use mostly (if not exclusively) Text document properties. The field switches needed to produce some commonly used formats are listed in the following table.
Raw Access Data Desired Word Format Field Code Switches
$11,523.75 February 2, 2001 28283-9898 829-88-7445 one hundred fifty thousand two hundred fifty and 50/100 ONE HUNDRED FIFTY THOUSAND TWO HUNDRED FIFTY AND 25/100 one hundred fifty thousand two hundred fifty Thirteenth November
DOCPROPERTY DollarAmount \# $###,##0.00 DOCPROPERTY DueDate \@ MMMM d, yyyy DOCPROPERTY ZipCode \# 000000000 DOCPROPERTY SSN \# 000-000000 DOCPROPERTY DollarAmount \* DollarText DOCPROPERTY DollarAmount \* DollarText \* Upper DOCPROPERTY EntryAmount \* CardText DOCPROPERTY StartDate \@ d \* OrdText \*FirstCap DOCPROPERTY StartDate \@ MMMM
TIP
You can create PostNet bar codes for U.S. zip codes on an envelope or label by adding a ZipCode DocProperty field to the Word template and applying a ZipCode bookmark to it. Because the WholeAddress field includes the zip code (or postal code, depending on the country), you should make the ZipCode DocProperty field invisible. To do this, select the field, open the Font Dialog by clicking the tiny arrow in the lower right of the Font group on the Word Ribbon, and check the Hidden checkbox. Next, position your cursor above the address block, select Insert Quick Parts Field, select the BarCode field, and then the ZipCode bookmark; leave the POSTNET bar code checkbox checked, and click OK to insert the bar code field (see the next figure).
152
TIP
When placing DocProperty fields in a template, make sure that the Preserve formatting during updates checkbox is not checkedif it is checked, and the text displayed from a doc property is longer than one word, the first word may have (probably will have, in my experience) a different font or size than the other words.
CROSS-REF
See the previous table for a listing of Word field switches used to format values in DocProperty fields.
153
Part II
The cmdWord_Click() event procedure first saves information to variables, for use later in the code, then checks that the template is found in the templates folder, sets a Word Application variable, and creates a new Word document based on the template. Next, it sets a reference to the Word CustomDocumentProperties collection of the newly created document and sets each document property to either a variable or the value in a field from the current record. The segment of code that creates a save name for the document (used in most of my export procedures) uses a Do...Loop statement to create a save name for the document containing the merged data, picking up the contact name from a field on the form, adding todays date, in a format that uses dashes to create an acceptable file name:
Private Sub cmdWord_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim appWord As Word.Application strCompanyName As String strContactName As String strWholeAddress As String strJobTitle As String docs As Word.Documents doc As Word.Document strWordTemplate As String strDocsPath As String strTemplatePath As String prps As Object strShortDate As String strLongDate As String strTest As String strAddress As String strCountry As String strSaveName As String strTestFile As String intSaveNameFail As Boolean i As Integer strSaveNamePath As String
154
strLongDate = Format(Date, mmmm d, yyyy) strShortDate = Format(Date, m-d-yyyy) strSaveName = Letter to & strContactName strSaveName = strSaveName & on & strShortDate _ & .doc strDocsPath = GetContactsDocsPath() Debug.Print Docs path: & strDocsPath strTemplatePath = GetContactsTemplatesPath() Debug.Print Template path: & strTemplatePath strWordTemplate = strTemplatePath & strWordTemplate Debug.Print Word template and path: _ & strWordTemplate
Check for the template in the selected Contact Templates folder, and exit if it is not found:
strTestFile = Nz(Dir(strWordTemplate)) Debug.Print Test file: & strTestFile If strTestFile = Then MsgBox strWordTemplate _ & template not found; cant create letter GoTo ErrorHandlerExit End If
Set the Word Application variable; if Word is not running, the error handler defaults to CreateObject:
Set Set Set Set appWord = GetObject(Class:=Word.Application) docs = appWord.Documents doc = docs.Add(strWordTemplate) prps = doc.CustomDocumentProperties
Turn off error handler because some of the templates may not have all of the doc properties:
On Error Resume Next prps.Item(NameTitleCompany).Value = _ Nz(Me![NameTitleCompany]) prps.Item(WholeAddress).Value = _ Nz(Me![WholeAddress]) prps.Item(Salutation).Value = _ Nz(Me![Salutation]) prps.Item(TodayDate).Value = strLongDate prps.Item(CompanyName).Value = _ strCompanyName prps.Item(JobTitle).Value = _ Nz(Me![JobTitle]) prps.Item(ZipCode).Value = _ Nz(Me![ZipCode]) prps.Item(ContactName).Value = strContactName On Error GoTo ErrorHandler
155
Part II
Check for a previously saved letter in the documents folder, and append an incremented number to the save name if one is found:
i = 2 intSaveNameFail = True Do While intSaveNameFail strSaveNamePath = strDocsPath & strSaveName Debug.Print Proposed save name and path: _ & vbCrLf & strSaveNamePath strTestFile = Nz(Dir(strSaveNamePath)) Debug.Print Test file: & strTestFile If strTestFile = strSaveName Then Debug.Print Save name already used: _ & strSaveName
156
Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Sub
TIP
When creating Word documents in VBA code, I save the current date to the TodayDate document property in the export code, rather than inserting a date code into the Word template, to ensure that the date on the letter will always be the date the letter was created; a Date field will show the current date (the date the letter is reopened).
If the name has already been used, the code loops back and adds a number to the end of the save name, and keeps trying until an unused number is reached. This technique means that you wont overwrite documents created the same day, but instead will create a series of documents with incrementing numbers.
FIGURE 6.16 A Word letter filled with Access data from a single contact record.
157
Part II
If you dont want to create multiple documents, you can eliminate the Do...Loop statement and overwrite an existing file with the same name, if there is one.
FIGURE 6.17 A form for selecting a document and recipients for creating Word documents filled with Access data from multiple contact records.
The Select Document combo box list shows the merge type in the second column (see Figure 6.18). The procedure on the cmdMerge buttons Click event first determines that a template has been selected, and that the template can be found in the Contact Templates folder (this folder is set on the databases main menu). Next, the merge method is picked up from the third column of the combo boxs list (the first column is not displayed; it contains the file name of the selected document, for use in code). Because some of the merge documents are Word documents, and some are templates, and some are in Word 2007 format and others in Word 97/2003 format, there is an If...Then statement in the procedure that examines the original documents extension, and creates the appropriate save document extension for use as an argument for the called procedures.
158
FIGURE 6.18 A combo box for selecting a Word template for merging data from Access.
The procedure then calls one of four procedures with the document file name (including path) and extension as arguments, depending on the merge type. The cmdCreateDocuments_Click procedure is listed as follows:
Private Sub cmdCreateDocuments_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim cbo As Access.ComboBox strCompanyName As String strContactName As String strJobTitle As String strTestFile As String strWordTemplate As String strTest As String strDocType As String strMergeType As String strExtension As String
159
Part II
Check for the template in the selected template folder, and exit if it is not found:
strTestFile = Nz(Dir(strWordTemplate)) Debug.Print Test file: & strTestFile If strTestFile = Then MsgBox strWordTemplate & template not found; _ & cant create document GoTo ErrorHandlerExit End If
If a document of the Doc Props merge type is selected, the MergeDocProps procedure is called. This procedure first sets a reference to the handy ItemsSelected collection of the ListBox (this collection includes only the rows selected in the ListBox), then iterates through the collection, creating a new Word document for each contact record.
160
The code then sets a reference to the Word CustomDocumentProperties collection of the newly created document, and sets each document property to the value in a column of the current row from the ListBox. A save name is created, including the document title, contact name, company name, and date, and the document is saved:
Private Sub MergeDocProps(strWordTemplate As String, _ strExtension As String) On Error GoTo ErrorHandler strLongDate = Format(Date, mmmm d, yyyy) strShortDate = Format(Date, m-d-yyyy) strDocsPath = GetContactsDocsPath() Debug.Print Docs path: & strDocsPath
161
Part II
Turn off error handler because some templates dont have all of the doc properties:
On Error Resume Next prps.Item(NameTitleCompany).Value = _ Nz(lst.Column(2, varItem)) prps.Item(WholeAddress).Value = _ Nz(lst.Column(5, varItem)) prps.Item(Salutation).Value = _ Nz(lst.Column(10, varItem)) prps.Item(TodayDate).Value = strLongDate prps.Item(CompanyName).Value = _ strCompanyName prps.Item(JobTitle).Value = _ Nz(lst.Column(8, varItem)) prps.Item(ZipCode).Value = _ Nz(lst.Column(6, varItem)) prps.Item(ContactName).Value = strContactName On Error GoTo ErrorHandler
Check for a previously saved document in the documents folder, and append an incremented number to the save name if one is found:
strDocType = _ appWord.ActiveDocument. BuiltInDocumentProperties(wdPropertyTitle) strSaveName = strDocType & to _ & strContactName & - & strCompanyName strSaveName = strSaveName & on _ & strShortDate & strExtension i = 2 intSaveNameFail = True Do While intSaveNameFail strSaveNamePath = strDocsPath & strSaveName Debug.Print Proposed save name and path: _ & vbCrLf & strSaveNamePath strTestFile = Nz(Dir(strSaveNamePath)) Debug.Print Test file: & strTestFile If strTestFile = strSaveName Then Debug.Print Save name already used: _ & strSaveName
162
163
Part II
Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Sub
TIP
For a list of the built-in Word named constants that can be used as arguments for functions or methods, or set as the values of properties, look up the appropriate enumeration (enum) in the Object Browser. Word enums start with Wd and are at the bottom of the Classes list. For example, to see what named constants can be used for the WindowState property of the ActiveWindow property, look up the WdWindowState enum, which is shown in Figure 6.19.
The cmdDeselectAll_Click procedure, run from the Clear All Selections command button, deselects all the rows in the listbox, even the ones you cant see, so you can start fresh:
Private Sub cmdDeselectAll_Click() On Error GoTo ErrorHandler Set lst = Me![lstSelectContacts] intRows = lst.ListCount - 1
164
For intIndex = 0 To intRows lst.Selected(intIndex) = False Next intIndex ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The cmdSelectAll_Click procedure, run from the Select All Names command button, selects all the rows in the listbox; its code is similar, setting the Selected value to True instead of False. Clicking the Clear All Selections command button clears any selections you have made in the listbox; clicking the Create Documents command button starts the merge, calling one of four procedures, depending on the merge type.
Word Bookmarks
If you select the Contact Letter with Envelope (Bookmarks) template from the Select Document combo box on frmMergeToWord, you will get a set of individual letters, one to each selected contact, with bookmarks filled with Access data. One of these letters is shown in Figure 6.20 (I made bookmarks visible so you can see their locations; note the gray I-bars). The MergeBookmarks procedure (listed next) is basically similar to the MergeDocProps code listed in an earlier section; the difference is that instead of working with the CustomDocumentProperties collection, it works with the Bookmarks collection, writing information from either variables or the listbox to named bookmarks in the newly created Word document:
Private Sub MergeBookmarks(strWordTemplate As String, _ strExtension As String) On Error GoTo ErrorHandler strLongDate = Format(Date, mmmm d, yyyy) strShortDate = Format(Date, m-d-yyyy) strDocsPath = GetContactsDocsPath() Debug.Print Docs path: & strDocsPath
165
Part II
166
Write information to Word bookmarks, first turning off the error handler because some templates dont have all of these bookmarks:
On Error Resume Next With appWord.Selection .GoTo what:=wdGoToBookmark, _ Name:=NameTitleCompany .TypeText Text:=strNameTitleCompany .GoTo what:=wdGoToBookmark, _ Name:=WholeAddress .TypeText Text:=strWholeAddress .GoTo what:=wdGoToBookmark, Name:=Salutation .TypeText Text:=Nz(lst.Column(10, varItem)) .GoTo what:=wdGoToBookmark, Name:=TodayDate .TypeText Text:=strLongDate
167
Part II
.GoTo what:=wdGoToBookmark, _ Name:=EnvelopeNameTitleCompany .TypeText Text:=strNameTitleCompany .GoTo what:=wdGoToBookmark, _ Name:=EnvelopeWholeAddress .TypeText Text:=strWholeAddress .GoTo what:=wdGoToBookmark, Name:=ZipCode .TypeText Text:=Nz(lst.Column(6, varItem)) End With
Re-insert bookmark:
With appWord.Selection .MoveLeft _ unit:=wdWord, Count:=3, _ Extend:=wdExtend .Font.Hidden = True End With Re-hide zip code. With ActiveDocument.Bookmarks .Add Range:=Selection.Range, Name:=ZipCode .DefaultSorting = wdSortByName .ShowHidden = False End With On Error GoTo ErrorHandler
Check for a previously saved document in the documents folder, and append an incremented number to the save name if one is found:
strDocType = _ appWord.ActiveDocument. BuiltInDocumentProperties(wdPropertyTitle) strSaveName = strDocType & to _ & strContactName & - & strCompanyName strSaveName = strSaveName & on _ & strShortDate & strExtension i = 2 intSaveNameFail = True Do While intSaveNameFail strSaveNamePath = strDocsPath & strSaveName Debug.Print Proposed save name and path: _ & vbCrLf & strSaveNamePath strTestFile = Nz(Dir(strSaveNamePath)) Debug.Print Test file: & strTestFile If strTestFile = strSaveName Then Debug.Print Save name already used: _ & strSaveName
168
169
Part II
FIGURE 6.21 An Avery 5160 labels document filled with data from Access.
NEW FEATURE
You can also create a list-type document using the TypeText method, filling a table with data from Access records, one record per row. Figure 6.22 shows such a document, using one of the new Word 2007 table themes and the new banded rows feature.
The MergeTypeText procedure (listed as follows) writes data from variables directly to cells in a table, moving to the next cell using the MoveRight method:
Private Sub MergeTypeText(strWordTemplate As String, _ strExtension As String) On Error GoTo ErrorHandler
170
strLongDate = Format(Date, mmmm d, yyyy) strShortDate = Format(Date, m-d-yyyy) strDocsPath = GetContactsDocsPath() Debug.Print Docs path: & strDocsPath
FIGURE 6.22 A Contact list filled with data from Access, showing new Word 2007 formatting features.
171
Part II
Determine whether the template is for labels or list, and move to the first data cell in table if needed:
If Nz(InStr(strWordTemplate, List)) > 0 Then appWord.Selection.GoTo what:=wdGoToTable, _ which:=wdGoToFirst, _ Count:=1 appWord.Selection.MoveDown unit:=wdLine, _ Count:=1 End If
Set the intMod value depending on the number of cells per row:
strDocType = _ doc.BuiltInDocumentProperties(wdPropertyTitle) Select Case strDocType Case Avery 5160 Labels intMod = 3 Case Avery 5161 Labels intMod = 2 Case Avery 5162 Labels intMod = 2 End Select
172
End If strNameTitleCompany = _ Nz(lst.Column(2, varItem)) strWholeAddress = _ Nz(lst.Column(5, varItem)) strContactName = _ Nz(lst.Column(1, varItem)) strCompanyName = _ Nz(lst.Column(7, varItem)) strJobTitle = Nz(lst.Column(8, varItem))
Use the Mod operator to handle every second or third record differently, in order to write data only to valid cells:
lngSkip = lngCount Mod intMod If lngSkip <> 0 Then .MoveRight unit:=wdCell, Count:=2 ElseIf lngSkip = 0 Then .MoveRight unit:=wdCell, Count:=1 End If End With End If
173
Part II
Check for the previously saved document in the documents folder, and append an incremented number to the save name if one is found:
strDocType = _ appWord.ActiveDocument.BuiltInDocumentProperties(wdPropertyTitle) strSaveName = strDocType & on _ & strShortDate & strExtension i = 2 intSaveNameFail = True Do While intSaveNameFail strSaveNamePath = strDocsPath & strSaveName Debug.Print Proposed save name and path: _ & vbCrLf & strSaveNamePath strTestFile = Nz(Dir(strSaveNamePath)) Debug.Print Test file: & strTestFile If strTestFile = strSaveName Then Debug.Print Save name already used: _ & strSaveName
174
Mailing Labels
Sheets of mailing labels are very suitable for mail merge; you can select three types of Avery mailing labels (Mail Merge type) from the Select Document combo box on frmMergeToWord. If you select the Avery 5160 Labels (Mail Merge) document, you will get a Word document like the one shown in Figure 6.23. The Avery labels documents look (and work) exactly the same whether produced by mail merge or the TypeText method, so I prefer to use the TypeText method, because it is much simpler to set up a plain labels document than a mail merge document; all you have to do is create a labelstype document in Word, whereas creating a mail merge document requires manually linking to a data source and inserting merge fields.
175
Part II
FIGURE 6.23 An Avery 5161 mailing labels merge document filled with Access data.
Lists
The Word Catalog-type merge is very useful for producing lists of merged data. Selecting the Contact List (Mail Merge) document (this is a Word 97/2003 document, used in Compatibility mode) from the Select Document combo box on frmMergeToWord creates a list in tabular form, shown in Figure 6.24.
Documents
You can use mail merge to create a merge document, where each page displays data from one contact record. I prefer to use the document properties or bookmarks method, in order to have a separate document for each contact, but if you have many hundreds (or thousands) of documents to generate, this is not practical. The Contact Letters (Mail Merge) selection in the Select Document combo box creates a mail merge document with a letter on each page; Figure 6.25 shows one page of this merge document.
176
FIGURE 6.24 The Contact List filled with merged data from Access.
WARNING
If a merge document with the Merge Data.txt data source is open, if you try to create another merge document bound to the same data source, you will get an error message saying that Merge Data.txt already exists, and you wont be able to continue. To prevent this from happening, the mail merge code closes the original merge document after merging the data to a new document.
The MailMergeTextFile procedure is listed as follows. This procedure first fills an Access table with data from the ItemsSelected collection of the ListBox, and then exports the data from that table to a text file, Merge Data.txt, which is used as the data source for the mail merge document. The merge is executed to a new document (so the document can be opened later, without needing the data source) and saved to a name picked up from the templates Title field and the date, formatted with dashes to avoid file name problems:
177
Part II
FIGURE 6.25 The third page of a mail merge form letters document.
Private Sub MailMergeTextFile(strWordTemplate As String, _ strExtension As String) On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim rst As DAO.Recordset dbs As DAO.Database strDBPath As String strTextFile As String strDocName As String strSalutation As String strZipCode As String strSQL As String strTable As String
178
strLongDate = Format(Date, mmmm d, yyyy) strShortDate = Format(Date, m-d-yyyy) strDocsPath = GetContactsDocsPath() Debug.Print Docs path: & strDocsPath strTemplatePath = GetContactsTemplatesPath()
Check that at least two contacts have been selected, because there is no point of doing a mail merge to only one contact:
Set lst = Me![lstSelectContacts] If lst.ItemsSelected.Count < 0 Then MsgBox Please select at least one contact lst.SetFocus GoTo ErrorHandlerExit End If
179
Part II
strContactName = _ Nz(lst.Column(1, varItem)) strCompanyName = _ Nz(lst.Column(7, varItem)) strNameTitleCompany = _ Nz(lst.Column(2, varItem)) strWholeAddress = Nz(lst.Column(5, varItem)) strSalutation = Nz(lst.Column(10, varItem)) strJobTitle = Nz(lst.Column(8, varItem)) strZipCode = Nz(lst.Column(6, varItem))
Add records to the table from the selected items in the ListBox:
With rst .AddNew ![NameTitleCompany] = strNameTitleCompany ![WholeAddress] = strWholeAddress ![Salutation] = strSalutation ![TodayDate] = strLongDate ![CompanyName] = strCompanyName ![JobTitle] = strJobTitle ![ZipCode] = strZipCode ![ContactName] = strContactName .Update End With NextContact: Next varItem rst.Close
Export the merge table data to a text file, to be used as the mail merge documents data source:
strTextFile = strTemplatePath & Merge Data.txt Debug.Print Text file for merge: & strTextFile DoCmd.TransferText transfertype:=acExportDelim, _ TableName:=strTable, _ FileName:=strTextFile, _ HasFieldNames:=True
180
Check for a previously saved document in the documents folder, and append an incremented number to the save name if one is found:
strDocType = _ appWord.ActiveDocument. BuiltInDocumentProperties(wdPropertyTitle) strSaveName = strDocType & on _ & strShortDate & strExtension i = 2 intSaveNameFail = True Do While intSaveNameFail strSaveNamePath = strDocsPath & strSaveName Debug.Print Proposed save name and path: _ & vbCrLf & strSaveNamePath strTestFile = Nz(Dir(strSaveNamePath)) Debug.Print Test file: & strTestFile If strTestFile = strSaveName Then Debug.Print Save name already used: _ & strSaveName
Set the merge data source to the text file just created, and do the merge:
With appWord .ActiveDocument.MailMerge.OpenDataSource _ Name:=strTextFile, _ Format:=wdOpenFormatText .ActiveDocument.MailMerge.Destination = _ wdSendToNewDocument .ActiveDocument.MailMerge.Execute
181
Part II
Summary
Now you know how to export Access data to various types of Word documents, both in the interface and in VBA code, you can produce highly formatted Word documents filled with Access data. Microsoft promotes mail merge, but in my opinion it is best to avoid this method (especially in Office 2007 and Windows Vista), because of the problems it has with security features. With the three other techniques covered in this chapter, you will know how to produce almost any type of document using other methods, and avoid the problems with locked or unavailable databases when using mail merge when it is needed.
182
IN THIS CHAPTER
The Excel object model Creating worksheets from the Ribbon Creating worksheets from templates Formatting worksheets in VBA code Filling Excel worksheets with Access timesheet data
NOTE
183
Part II
Clicking the Excel button opens a dialog where you can browse for the location for saving the worksheet. This dialog has an option for preserving the layout and formatting of the original Access object, which (curiously) is only available if a table or query is selected (see Figure 7.2). Tables and queries dont have much in the way of formatting and layout, but if you check the Export data with formatting and layout checkbox your Excel worksheet will use the same font as the table or query (though not the alternate row shading), and it will show data from a linked table instead of the linking ID field. Figure 7.3 shows two worksheets made from the same table; the top one displays the customers company name in the CustomerID column (picking it up from the linked table) and uses the Calibri 11 font; the second worksheet displays the CustomerID in that column and uses the Arial 10 font.
184
FIGURE 7.3 Excel worksheets exported with and without checking the Export data with formatting and layout option on the export dialog.
185
Part II
The Excel button on the Ribbon (with or without the layout and formatting preserved) is a useful option when you need to create a quick-and-dirty Excel worksheet in Excel 2007 format (the new extension is .xlsx). If you need to export to an older Excel format, but you dont need fancy formatting, you can do an export with a single line of VBA code, using the TransferSpreadsheet method, which has been available since the earliest days of Access. The TransferSpreadsheet method allows you to select the Excel version for creating your worksheet filled with Access data, so you can create worksheets that will be usable by recipients who have older versions of Office. The procedure listed as follows exports tblContacts to an Excel 97-2003 worksheet called Contacts.xls (the named constant for this worksheet version is acSpreadsheetTypeExcel7):
Public Function TransferToExcel() On Error GoTo ErrorHandler Dim strTable As String Dim strWorksheetPath As String strWorksheetPath = GetWorksheetsPath strWorksheetPath = strWorksheetPath & Contacts.xls strTable = tblContacts strWorksheetPath = GetWorksheetsPath() strWorksheetPath = strWorksheetPath & Contacts.xls Debug.Print Worksheet path: & strWorksheetPath
ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The exported worksheet looks just like the unformatted worksheet exported from the Ribbon selection, but it is in the older format, as you can see from the (Compatibility Mode) after the worksheet name in its title bar (see Figure 7.4).
186
NOTE
The Excel Export.accdb sample database contains the tables, queries, forms, and code used in this chapter.
The CreateObject and GetObject functions are used to either create a new Excel object, or set a reference to an existing instance of Excel. Using GetObject to retrieve a reference to an existing workbook avoids creating extra instances of Excel, which uses up system resources. To open a worksheet within a workbook, first set a reference to the Workbook object, then add a new workbook to the Workbooks collection. By default, the new workbook will have three worksheets. The CreateNewWorkbook procedure creates a new, blank workbook from the default Excel template, with three worksheets:
Public Function CreateNewWorkbook () On Error GoTo ErrorHandler Dim appExcel As Excel.Application Dim bks As Excel.Workbooks Set appExcel = GetObject(, Excel.Application) Set bks = appExcel.Workbooks
187
Part II
NOTE
The procedures in this section can be run from macros; each procedure has a macro whose name is mcr plus the procedure name.
The error handler in the preceding procedure is similar to the one used in the Word Automation code in Chapter 6; sets a reference to the current Excel instance if Excel is running and otherwise creates a new Excel Application instance using the CreateObject function from a line in the procedures error handler. If you run the CreateNewWorkbook procedure repeatedly, each time it runs a new workbook is created within the Excel window, named Book1, Book2, and so forth, as shown in Figure 7.5.
188
To open a specific saved workbook, use a procedure like the one listed next, which opens a saved workbook using the Worksheets collections Open method and the file name and path:
Public Function OpenSpecificWorkbook() On Error GoTo ErrorHandler Dim Dim Dim Dim appExcel As Excel.Application bks As Excel.Workbooks sht As Excel.Worksheet strWorkbook As String
Set appExcel = GetObject(, Excel.Application) strPrompt = Enter path and title of workbook strTitle = Workbook Name strDefault = D:\Documents\tblContacts2.xls strWorkbook = InputBox(prompt:=strPrompt, _ Title:=strTitle, Default:=strDefault) appExcel.Workbooks.Open (strWorkbook) Set sht = appExcel.ActiveWorkbook.Sheets(1)
189
Part II
If Excel is open, the workbook will be opened in the same window, as shown in Figure 7.6.
190
If Excel is not running, the workbook will open in a new Excel window. As with exporting Access data to Word, when writing Automation code to work with Excel, you only need a few components of the Excel object model: Workbooks, Worksheets, the Range object, Rows, and Columns. These objects are used in the more complex Excel Automation procedures in the following sections.
Set appExcel = GetObject(, Excel.Application) strTemplatePath = GetWorksheetTemplatesPath strTemplateFile = Access Contacts.xltx strTemplateFileAndPath = strTemplatePath _ & strTemplateFile
191
Part II
Check for the template in the selected template folder, and exit if not found:
strTestFile = Nz(Dir(strTemplateFileAndPath)) Debug.Print Test file: & strTestFile If strTestFile = Then MsgBox strTemplateFileAndPath _ & template not found; _ & cant create worksheet GoTo ErrorHandlerExit End If strWorksheetPath = GetWorksheetsPath Debug.Print Worksheet template and path: _ & strTemplateFileAndPath
Set a reference to the workbook and worksheet, and activate the worksheet:
Set bks = appExcel.Workbooks Set wkb = bks.Add(strTemplateFileAndPath) Set wks = wkb.Sheets(1) wks.Activate
Loop through the recordset, importing each record to a cell in the worksheet:
With rst Do Until .EOF
192
193
Part II
The procedure first picks up the worksheet template path from the main menu, checks that the template is to be found in that location, and then creates a new workbook from the template. It then sets up a recordset based on an Access query, goes to the first data cell in the worksheet, and starts iterating through the records in the recordset, using the Offset method of the active cell to place data from each field in the correct column.
194
When all the contacts have been exported to the worksheet, a save name is constructed from the templates Title property and the current date, and displayed in an InputBox so it can be edited, if desired; finally, the worksheet is saved with the save name and made visible.
NOTE
The ExportContactsToExcel procedure in this section can be run from the macro mcrExportContactsToExcel.
195
Part II
sht As Excel.Worksheet wkb As Excel.Workbook rng As Excel.Range strTable As String strRange As String strSaveName As String strPrompt As String strTitle As String strDefault As String
196
197
Part II
198
.Range(A2:F2).VerticalAlignment = xlBottom .Range(A2:F2).WrapText = False .Range(A2:F2).Orientation = 0 .Range(A2:F2).ShrinkToFit = False .Range(A2:F2).MergeCells = True .Range(A2:F2).Borders(xlDiagonalDown).LineStyle = _ xlNone .Range(A2:F2).Borders(xlDiagonalUp).LineStyle = _ xlNone .Range(A2:F2).Borders(xlEdgeLeft).LineStyle = xlNone .Range(A2:F2).Borders(xlEdgeTop).LineStyle = xlNone .Range(A2:F2).Borders(xlEdgeBottom).LineStyle = _ xlNone .Range(A2:F2).Borders(xlEdgeRight).LineStyle = xlNone .Range(A2:F2).Borders(xlInsideVertical).LineStyle = _ xlNone .Range(A3:F3).HorizontalAlignment = xlCenter .Range(A3:F3).VerticalAlignment = xlBottom .Range(A3:F3).WrapText = False .Range(A3:F3).Orientation = 0 .Range(A3:F3).ShrinkToFit = False .Range(A3:F3).MergeCells = True .Range(A3:F3).Borders(xlDiagonalDown).LineStyle = _ xlNone .Range(A3:F3).Borders(xlDiagonalUp).LineStyle = _ xlNone .Range(A3:F3).Borders(xlEdgeLeft).LineStyle = xlNone .Range(A3:F3).Borders(xlEdgeTop).LineStyle = xlNone .Range(A3:F3).Borders(xlEdgeBottom).LineStyle = _ xlNone .Range(A3:F3).Borders(xlEdgeRight).LineStyle = xlNone .Range(A3:F3).Borders(xlInsideVertical).LineStyle = _ xlNone .Range(A4:F4).MergeCells = True .Range(A4:F4).Borders(xlDiagonalDown).LineStyle = _ xlNone .Range(A4:F4).Borders(xlDiagonalUp).LineStyle = _ xlNone .Range(A4:F4).Borders(xlEdgeLeft).LineStyle = xlNone .Range(A4:F4).Borders(xlEdgeTop).LineStyle = xlNone .Range(A4:F4).Borders(xlEdgeRight).LineStyle = xlNone .Range(A4:F4).Borders(xlInsideVertical).LineStyle = _ xlNone .Range(A1:A4).Font.Size = 14 .Range(A1:A4).Font.Bold = True .Range(A3).Value = As of & Date .Range(A2).Value = Account Services .Range(A1).Value = Nation-wide Summary of
199
Part II
200
NOTE
Because the workbook was created in an older format, you will see (Compatibility Mode) in its title bar.
The procedure starts by running a make-table query to create a table for export to Excel, then creates a save name for the worksheet, and deletes the old worksheet file, if it exists. The data in the table created by the make-table query is then exported to a new Excel worksheet, using the TransferSpreadsheet method. The new worksheet is opened and activated, and various ranges in the worksheet are formatted, applying the Calibri font, hairline borders, and appropriate column widths for each column.
TIP
I like to give tables created by make-table queries the prefix tmak, with the same base name as the query. This lets me know that a table was created by a make-table query, so I know that if I want to change it, I need to modify the query, not the table.
Next, the procedure inserts blank rows at the top of the worksheet, and title text is inserted at the top; these header lines are then formatted with a gray background and upper and lower lines. Several print setup and margin settings are done next, and finally the worksheet is saved, with an InputBox so you can modify the save name, if desired. The finished worksheet is shown in Figure 7.8.
201
Part II
TIP
As a quick way to find out the syntax for various Excel commands, open an Excel worksheet, turn on the macro recorder, perform the actions, and then save the macro. Open the saved macro and copy the code to your Access procedure; with a little trimming of redundant arguments and editing to insert your variable names, it should work fine.
Timesheets
Almost any type of business (other than a one-person operation) needs a form for recording employees work hours and a way to print or electronically distribute the timesheet data. Often a company has used a paper form to record work hours for many years, and the electronic form needs to replicate the paper form. In some cases, there are specific government or industry standard formats that must be used, or the data must be produced in a format that can be imported by a mainframe computer. You can use a preformatted Excel worksheet template to produce timesheets in the exact format you need and fill them with data from Access. One example of using timesheets in such a fashion is an engineering firm whose employees work on various projects for the companys clients. Because the employees work hours (except for those assigned to internal projects) will be billed back to the clients, in this case a separate worksheet is needed for each employees work on a specific project per week, so a single employee might have several timesheets in a week. In the case of (for example) a scientific research establishment, where hours are not billed out to clients, one timesheet per employee, listing multiple projects in a week, would be more appropriate. The form frmWeeklyTimesheet (shown in Figure 7.9) is an Access front end for entering timesheet data that will be exported to Excel timesheets. This form lets you select an employee, client, and project, and fill in a timesheet for that employee. The assumption is that a separate timesheet is done for each client/project combination, so an employee can have multiple timesheets for a given week. The cboEmployeeID combo boxs row source is a union query that combines data from two queries: qryThisWeeksTimesheets, which lists the timesheets that have been filled in so far this week, and qryNeedTimesheets, which lists the employees who have not yet filled out a timesheet for this week. The resulting list displays all the employees, showing the timesheets that have been filled out so far, as illustrated in Figure 7.10.
202
FIGURE 7.9 An Access form for entering timesheet data for export to Excel.
After selecting an employee, the CurrentWeekEnding procedure calculates the week ending date (today, if it is Sunday, otherwise last Sunday) and fills the captions of the seven date labels on the form with the correct day of the week; the Manager name is also displayed in the Manager field (the light blue back color indicates that the text box is locked). (See Figure 7.11.)
NOTE
controls.
I give locked controls a light blue background (as opposed to a white background for editable controls) to give users a visual cue that they cant enter or edit text in these
203
Part II
204
Additionally, the code runs a make-table query that creates a table for use in the query that is the row source of cboClientProject (see Figure 7.12); initially, the combo boxs row source is blank, because otherwise the query could not be run. The row source query is a FindUnmatched query created with the Query Wizard that excludes client/project combinations for worksheets that have already been filled out for the selected employee, so you cant accidentally select the same one twice.
205
Part II
If you need to modify the data on an existing timesheet, you can do this later, in the review stage, from fdlgTimesheets. After the client and project has been selected, the hours can be entered; the totals will recalculate automatically (see Figure 7.13).
206
In the form footer there are three command buttons: the first (Clear This Timesheet) clears the timesheet so you can start over; the second (Save This Timesheet) saves the current timesheet and starts a new record for entering another timesheet; and the third (Send Timesheets to Excel) opens a dialog form listing the timesheets that have been completed for the current week, for review. The three command button event procedures are listed as follows:
Private Sub cmdClearTimesheet_Click() On Error Resume Next
The frmWeeklyTimesheet form is bound to a temp table, tblWeeklyTimesheetTemp, to ensure that data wont be saved to the regular table (tblWeeklyTimesheet) until the user chooses to save it, and required fields have been filled in:
Private Sub cmdSaveTimesheet_Click() On Error GoTo ErrorHandler
207
Part II
MsgBox prompt:=strPrompt, Buttons:=vbExclamation _ + vbOKOnly, Title:=strTitle Me![cboEmployeeID].SetFocus GoTo ErrorHandlerExit End If If Nz(Me![cboClientProject].Value) = Then strPrompt = Please select a client and project MsgBox prompt:=strPrompt, Buttons:=vbExclamation _ + vbOKOnly, Title:=strTitle Me![cboClientProject].SetFocus GoTo ErrorHandlerExit End If
208
DoCmd.RunCommand acCmdDeleteRecord Me![cboEmployeeID].Requery Me![cboClientProject].RowSource = ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The dialog form opened from the Send Timesheets to Excel button is shown in Figure 7.14.
The txtEmployeeID textbox on the datasheet subform on the dialog form has a DblClick event procedure, so you can double-click an employee name to open that timesheet for editing, if necessary:
Private Sub txtEmployeeID_DblClick(Cancel As Integer) On Error GoTo ErrorHandler Dim lngID As Long Dim strClientCode As String
209
Part II
strProjectCode As String strSearch As String strSQL As String frm As Access.Form strForm As String
Create a filtered query and run it to create the forms record source:
strForm = frmSelectedTimesheet lngID = Nz(Me![EmployeeID]) strClientCode = Nz(Me![ClientCode]) strProjectCode = Nz(Me![ProjectCode]) strSQL = SELECT tblWeeklyTimesheet.*, _ & qryEmployees.EmployeeName, _ & qryEmployees.ManagerName, _ & qryClientsAndProjects.ClientProject _ & INTO tmakSelectedTimesheetTemp _ & FROM qryClientsAndProjects _ & INNER JOIN (tblWeeklyTimesheet _ & INNER JOIN qryEmployees _ & ON tblWeeklyTimesheet.EmployeeID = _ & qryEmployees.EmployeeID) _ & ON (qryClientsAndProjects.ProjectCode = _ & tblWeeklyTimesheet.ProjectCode) _ & AND (qryClientsAndProjects.ClientCode = _ & tblWeeklyTimesheet.ClientCode) _ & WHERE tblWeeklyTimesheet.EmployeeID= _ & lngID & AND tblWeeklyTimesheet.ClientCode= _ & Chr$(39) & strClientCode & Chr$(39) _ & AND tblWeeklyTimesheet.ProjectCode= _ & Chr$(39) & strProjectCode & Chr$(39) _ & AND tblWeeklyTimesheet.WeekEnding = _ & CurrentWeekEnding(); Debug.Print SQL string: & strSQL DoCmd.SetWarnings False DoCmd.RunSQL strSQL
210
ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The frmSelectedTimesheet form is a simplified version of the frmWeeklyTimesheet (see Figure 7.15).
The hours can be edited on this form, and when you are done you can either delete this timesheet record by clicking the Clear This Timesheet button, or save the record to the regular tblWeeklyTimesheets table. Clicking the Send Timesheets to Excel button reopens the fdlgTimesheets dialog, with updated data. Clicking the OK button on fdlgTimesheets runs the CreateExcelTimesheets procedure, which creates one Excel worksheet for each timesheet listed in the dialog; one of these timesheets is shown in Figure 7.16.
211
Part II
The CreateExcelTimesheets procedure listed as follows first sets up a DAO recordset of the current weeks timesheets, and another recordset for that employees hours. The employee information is entered on the worksheet first, then the code iterates through the Hours recordset, processing the hours for each project and day, both regular hours and overtime, until all have been filled in, and then loops to the next employee record:
Function CreateExcelTimeSheets() On Error GoTo ErrorHandler
212
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim
appExcel As Excel.Application dteWeekEnding As Date lngCount As Long lngEmployeeID As Long n As Long rngCC As Excel.Range rngDay As Excel.Range rngOT As Excel.Range rngPC As Excel.Range rngRH As Excel.Range rngTotal As Excel.Range rngTotalAbove As Excel.Range rstAll As DAO.Recordset rstOne As DAO.Recordset strDocsPath As String strEmployeeName As String strPrompt As String strQuery As String strSaveName As String strSheet As String strSQL As String strTemplate As String strTemplateFile As String strTemplatePath As String strTitle As String wkb As Excel.Workbook wks As Excel.Worksheet lbl As Access.Label
Set dbs = CurrentDb Set rstAll = _ dbs.OpenRecordset(qryCurrentTimesheetInfo, _ dbOpenDynaset) rstAll.MoveLast rstAll.MoveFirst lngCount = rstAll.RecordCount If lngCount = 0 Then MsgBox No current time sheet records to export GoTo ErrorHandlerExit Else Set lbl = _ Forms![fdlgTimesheets]![lblCreatingWorksheets] Debug.Print lngCount _ & current time sheet records to transfer to Excel lbl.Visible = True End If
213
Part II
Get the template path that was selected on the main menu:
strTemplate = _ Weekly time sheet by client and project.xlt strTemplatePath = GetWorksheetTemplatesPath() strTemplateFile = strTemplatePath & strTemplate If TestFileExists(strTemplateFile) = False Then strTitle = Template not found strPrompt = Excel template _ & Weekly time sheet by client and project.xlt _ & not found in & strTemplatePath & ; _ & vbCrLf _ & please put template in this folder and try again MsgBox strPrompt, vbCritical + vbOKOnly, strTitle GoTo ErrorHandlerExit Else Debug.Print Excel template used: _ & strTemplateFile End If
Set a reference to the Excel Application object for use in creating workbooks:
Set appExcel = GetObject(, Excel.Application) Do While Not rstAll.EOF
214
Process hours for first project. Check for hours worked on Monday:
If Nz(![MondayHours]) _ + Nz(![MondayOTHours]) > 0 Then appExcel.GoTo _ Reference:=wks.Range(Monday) Set rngCC = _ appExcel.ActiveCell.Offset(columnoffset:=2) Set rngPC = _ appExcel.ActiveCell.Offset(columnoffset:=3) Set rngRH = _ appExcel.ActiveCell.Offset(columnoffset:=4) Set rngOT = _ appExcel.ActiveCell.Offset(columnoffset:=5) rngCC.Value = ![ClientCode] rngPC.Value = ![ProjectCode] rngRH.Value = ![MondayHours] rngOT.Value = ![MondayOTHours] End If
215
Part II
Process different project hours for the same employee on the same worksheet. Check for extra hours worked on Monday:
If Nz(![MondayHours]) + _ Nz(![MondayOTHours]) > 0 Then
216
Save and close filled-in worksheet. Create workbook save name from employee name and week ending date:
strSaveName = strDocsPath & strEmployeeName _ & time sheet for week ending _ & Format(dteWeekEnding, d-mmm-yyyy) Debug.Print Time sheet save name: _ & strSaveName
217
Part II
Set appExcel = Nothing MsgBox All time sheet workbooks created in _ & strDocsPath ErrorHandlerExit: Exit Function
ErrorHandler:
This procedure creates a new Excel worksheet from a template for each record. This template is pre-filled with standard text, colors, and other features; all it needs is to have the timesheet data filled in from the Access record.
Summary
With the techniques described in this chapter, you can export data in Access tables to Excel worksheets in a variety of formats, for compatibility with older Office versions or handheld devices. You can use the Excel button on the Ribbon to do a quick-and-dirty export to the new .xlsx format, or create a worksheet in an older format that can be synchronized with a PDA, using the TransferSpreadsheet method. And finally, when you need to output your data to an Excel worksheet in a specific format, you can use a preformatted worksheet template, or format a plain worksheet using VBA Automation code to get the exact results you want.
218
utlook has a great interface for working with calendars, contacts, and tasks, as well as for sending email messages. But Outlook is a relative newcomer to Office (it was first introduced in Office 97), which means that if you have been using Access for longer than that, you probably have calendar, contact, or task data stored in Access tables in databases that were created many Office versions ago. (I have some that were originally created in Access 1.0!) In the case of contact information, there is another reason that many users prefer storing data in Access: Access is a relational database, allowing you to set up one-to-many links between companies and contacts, contacts and phones, contacts and addresses, and so forth. Outlook, in contrast, isnt a relational database; it stores all of its data in a flat-file MAPI database. Thats why you will see slots for three addresses on an Outlook contact, and a large (but finite) selection of Phone and ID slots. If you need to enter four addresses for a contact, you are out of luck. If you need to enter a type of phone number or ID that is not one of the available items, you cant do it. But if you store your contact data in Access, you can create linked tables of addresses, phone numbers, and IDs, letting you enter as many phones and IDs as you need per contact, and you can give them whatever identifiers you wish. And with a one-to-many link between companies and contacts, you can change a companys address or main phone number once, and the changed information will be picked up through the link for all of that companys contacts. In Outlook, by contrast, if you have 10 contacts for a company, and the companys address or main phone number changes, you have to make the change separately on all 10 contacts.
IN THIS CHAPTER
Linking to Outlook folders Learning about the Outlook object model Working with Outlook appointments Working with Outlook tasks Working with Outlook mail messages Working with Outlook contacts
219
Part II
As an example, the Microsoft record in my personal Access Contacts database has 30 phone numbers, many with non-standard descriptions I couldnt do that in Outlook! However, despite the advantages of a relational database, Outlook is undeniably attractive and convenient, so much so that you may want (or need) to export your Access contact data to Outlook contact items, so you can quickly look up a phone number or email address (or at least those that correspond to standard Outlook slots). And if you have tasks or calendar items stored in an Access table (perhaps created before Office 97), you may wish to permanently move them to Outlook, which offers a superior interface for working with these types of items.
CROSS-REF
See Chapter 11 for a detailed treatment of synchronizing a set of linked Access tables with matching Outlook contacts.
220
221
Part II
FIGURE 8.3 The Outlook Folder selection in the Import group on the Ribbon.
Selecting the Outlook Folder selection on the More menu opens a dialog box offering you three choices (shown in Figure 8.4): importing Outlook data into a new Access table, appending the data to an existing table, or linking the folder to a newly created Access table.
222
After selecting the Link to the data source by creating a new table option (shown in Figure 8.5) and clicking OK, the Link Exchange/Outlook Wizard opens, much the same as in previous versions of Office, letting you select a folder to link to an Access table.
FIGURE 8.5 Selecting the Tasks folder for linking to an Access table.
On the next screen of the wizard, you can give the table a name; I use the ol prefix to indicate that the table is linked to Outlook. I made linked tables for tasks, contacts, and appointments (located in the calendar folder, and named as such). Figure 8.6 shows three linked Outlook tables in the Tables list; note the arrow indicating a linked table, and the distinctive icon for Outlook.
As with earlier versions of Office, the Link option has serious limitations. The linked olTasks table (shown in Figure 8.7) has a great many fields, but it lacks the crucial Subject field, making it all but useless.
223
Part II
FIGURE 8.7 A linked Outlook Tasks folder, lacking the Subject field.
The linked olCalendar table (shown in Figure 8.8) does include the Subject field, as well as many mysterious and irrelevant fields such as MessageToMe and MessageCCToMe, but it lacks the crucial Start and End dates and times, so it is also useless.
Of the three, the linked olContacts table provides the best match for Outlook contacts: it does have most (but not all) of the standard contact item fields (though not the Customer ID field, which could be useful in linking records). However, the promise of linking Access tables to Outlook specifically that changes made in Access will be saved to Outlook, and vice versa is not completely fulfilled. For example, though I made changes to both the contact name and company name for a contact record in Access, only the company change was reflected back to Outlook. However, both contact and company name changes in Outlook were reflected to Access. Figure 8.9 shows the linked olContacts table.
224
o create a database from one of the new templates, first select New from the Access File menu, as shown in the following figure.
225
Part II
continued If the template you want to use is displayed in the next screen, select it directly.
226
227
Part II
continued
In the Access 2007 group, youll see several categories.
228
Click the template you want to use to open a page with a Download link.
229
Part II
NEW FEATURE
Microsoft has created several new database templates for Access 2007; one of them creates an Events database, and another creates the Tasks database. See the Creating a Database from a Template sidebar for full details on how to locate a database template and create a database from it.
Personally I prefer to maintain my calendar and task list in Outlook, because it has the richest interface for working with appointments and tasks. However, if you want (or need) to maintain a simple calendar or task list in Access, you might want to use these new sample databases, or import objects from them, into your database. You can examine the forms I imported from the sample databases by selecting them from the Forms section on the main menu of the Outlook Export database, as shown in Figure 8.10 (the imported forms end with List).
Figure 8.11 shows the Task List form, and Figure 8.12 shows the Event List form.
230
FIGURE 8.11 The Task List form, imported from a database created from the new Tasks database template.
FIGURE 8.12 The Event List form, imported from a database created from the new Events database template.
NOTE
The Outlook Export.accdb sample database contains the tables, queries, forms, and code used in this chapter.
There is also a Contact List form (in the Tasks sample database), shown in Figure 8.13. This form is only suitable for maintaining a simple flat-file contact list, and it lacks most of the special features Outlook provides for working with contacts, but again there are circumstances where you might want (or need) to keep contact information in a single Access table, such as when you need to regularly export basic contact data to a text file or worksheet for distribution via email, or for export to a mainframe flat-file database.
231
Part II
FIGURE 8.13 The Contact List form, imported from a database created from the new Tasks database template.
These forms from the new templates have buttons with embedded macros. The Add from Outlook button, which uses a new command argument, acCmdAddFromOutlook, opens a Select Names dialog for selecting a contact from Outlook, as shown in Figure 8.14.
232
NEW FEATURE
All of these forms from the new database templates use the new embedded macros instead of VBA code for various purposes. Using macros instead of code seems to me to be a step back to the earliest days of Access, but embedded macros do have the advantage of avoiding security problems that can occur if you try to run code that is not signed with a digital signature (or even when you run signed code, in Windows Vista). For simple tasks, such as closing a form or running a command or two, embedded macros work fine, but for more complex tasks, they wont do the job; you still need to write VBA code to perform complex tasks such as iterating through a recordset or creating new objects in other Office applications.
I also imported reports from the Tasks and Events databases, which you can view by selecting the report name from the Reports section of the sample databases main menu, shown in Figure 8.15.
NOTE
Depending on what type of printer you have, you may have to adjust the margins on some of these reports to avoid error messages when opening them.
233
Part II
234
The NameSpace, Explorer, and Inspector objects, in addition to the Application object itself, are the key to most of the Outlook components you need to work with, such as folders and items. To work with a folder, for example, you must first set a reference to the Outlook Application object, then the NameSpace object, and then you can retrieve one of the default local folders using the GetDefaultFolder method, or a custom folder by referencing it as a member of the top-level Folders collection, or some folder underneath that folder. (Note that the singular of Folders is now Folder apparently Microsoft realized that the previous name of MAPIFolder was causing confusion.)
NOTE
Although the MAPIFolder object has been replaced with the more intuitive Folder object in the Object Browser, you can still declare a folder object as MAPIFolder without causing a compile error. This means you dont need to go through all your old Outlook code and change MAPIFolder declarations to Folder.
235
Part II
in a folder, you also need to declare a variable as Object. This is because a variable of the Object type may represent items of different types; each item can be inspected, and if it is of the appropriate type, further action can be taken, as in my following code samples. To create an item of a specific type, use the Add method with a folders Items collection (this creates a standard item of the folders default item type), or use the Application objects CreateItem method with the appropriate constant (see Table 8.1 for lists of these constants). If you want to use custom objects, use the Application objects CreateItemFromTemplate object, with the name of the saved Outlook custom form. The code samples in the following sections illustrate uses of these methods. The following code fragments show you how to set a reference to an Outlook folder or item, with a number of variations. You can declare the Application variable using the New keyword, in which case you dont need to set the variable. Or you can declare it without the New keyword, and then set the variable later with GetObject or CreateObject, as I generally do in my complete procedures. A procedure generally starts with declaring a number of variables of different types; the following list of declarations covers the most commonly used high-level Outlook objects:
Dim Dim Dim Dim Dim Dim appOutlook As New Outlook.Application nms As Outlook.NameSpace flds As Outlook.Folders fld As Outlook.Folder exp As Outlook.Explorer ins As Outlook.Inspector
Declare a variable as Object so it can be used for any type of item. This is necessary if you need to reference the current item in a folder that may contain items of different types:
Dim itm As Object
Declare variables as specific item types, for use when you are creating or working with items of specific types:
Dim msg As Outlook.MailItem Dim con As Outlook.ContactItem Set nms = appOutlook.GetNamespace(MAPI)
The flds variable references the folders under the top-level folder:
Set flds = nms.Folders(Personal Folders).Folders
Create an item using the Add method of the Items collection of a folder. The item will be of the folders default item type:
Set appt = fldCalendar.Items.Add
236
Create a standard item using the CreateItem method of the Application object:
Set msg = appOutlook.CreateItem(olMailItem)
TIP
In previous versions of Office, you could open a saved Outlook template (.oft file) by simply double-clicking it in an Explorer window. This no longer works, because of more rigorous Microsoft security measures. In Office 2007 running on Windows Vista, if you try to open an Outlook template directly, you will get the warning message shown in Figure 8.17.
FIGURE 8.17 A warning message when opening a saved Outlook template file.
After clicking OK, the file will then open as a standard item, not your custom form. To get around this annoying security feature, select Tools Forms Choose Form in the main Outlook window, as shown in Figure 8.18.
237
Part II
In the Choose Form dialog, select User Templates in File System from the Look in drop-down list, as shown in Figure 8.19.
FIGURE 8.19 Selecting the User Templates in File System option to open a saved template file.
The template path defaults to the standard Office 2007/Windows Vista template location, C:\Users\Your Name\App Data\Roaming\Microsoft\Templates, but there is a Browse button that lets you select a template from another location. After selecting the template, you can open it with the Open button, and you will see your custom form at last (though its colors may have changed, because of changes in the Windows Vista and Office 2007 color palette).
Set a reference to the Folders collection of a folder called Custom Contacts under the top-level Personal Folders folder (via the previously set flds variable):
Set fld = flds(Custom Contacts)
Set a reference to the currently open folder, via the active Explorer:
Set exp = appOutlook.ActiveExplorer Set fld = exp.CurrentFolder
238
Set a reference to the currently open item, via the active Inspector:
Set ins = appOutlook.ActiveInspector Set itm = ins.CurrentItem
Test whether the open item is a mail message, and set a mail message variable to it if so:
If itm.Class = olMail Then Set msg = itm End If
Set a reference to a custom Outlook item property of the Yes/No data type:
blnCustomer = con.UserProperties(Customer)
Standard GetObject line and error handler to default to CreateObject in case Outlook is not running:
Dim appOutlook As Outlook.Application Set appOutlook = GetObject(, Outlook.Application)
239
Part II
Resume Next Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If
TABLE 8.1
The message class can be used to create an item of a specific type, or to determine what type of object you are dealing with (for example, in the active Inspector). It can also be seen in the Publish Form As dialog box when publishing an Outlook item to a library or folder, as shown in Figure 8.20. The message class of a custom form consists of the form name appended to the standard objects message class, with a separating period. The OlObjectClass named constants are used to determine what type of item you are dealing with, using the Class property of an object, whereas the OlItemType named constants are used for setting or determining the default item type of a folder, using a Folders DefaultItemType property. See the sample procedures in the next sections for examples that use these constants.
240
FIGURE 8.20 The Outlook Publish Form As dialog box, showing the message class of a custom Outlook form.
Set appOutlook = GetObject(, Outlook.Application) Set nms = appOutlook.GetNamespace(MAPI) Set fldCalendar = nms.GetDefaultFolder(olFolderCalendar)
241
Part II
Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblEvents) With rst Do While Not .EOF
Check for valid dates, and convert blank dates into 1/1/4501 (that is a blank date in Outlook).
If IsNull(![Start Time]) = True Then dteStartTime = #1/1/4501# Else dteStartTime = Nz(![Start Time]) End If If IsNull(![End Time]) = True Then dteEndTime = #1/1/4501# Else dteEndTime = Nz(![End Time]) End If
242
To import appointments from your local Outlook calendar into an Access table (tblImportedCalendar), use the following function (it can also be run from the mcrImportCalendar macro):
Public Function ImportApptsFromOutlook() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim fldCalendar As Outlook.Folder appt As Outlook.AppointmentItem strApptName As String dteStartTime As Date dteEndTime As Date strLocation As String strSQL As String strDescription As String
Set appOutlook = GetObject(, Outlook.Application) Set nms = appOutlook.GetNamespace(MAPI) Set fldCalendar = nms.GetDefaultFolder(olFolderCalendar)
243
Part II
Iterate through the appointments in the local Calendar folder and import them to the Access table.
For Each itm In fldCalendar.Items If itm.Class = olAppointment Then Set appt = itm With appt strApptName = Nz(.Subject) dteStartTime = Nz(.Start) dteEndTime = Nz(.End) strLocation = Nz(.Location) strDescription = Nz(.Body) End With With rst rst.AddNew ![Subject] = strApptName If dteStartTime <> #1/1/4501# Then ![Start Time] = dteStartTime End If If dteEndTime <> #1/1/4501# Then ![End Time] = dteEndTime End If ![Location] = strLocation ![Description] = strDescription .Update End With End If Next itm rst.Close DoCmd.OpenTable tblImportedCalendar ErrorHandlerExit: Exit Function ErrorHandler:
244
Figure 8.22 shows the table of imported appointments, which is automatically opened at the end of the procedure.
For a more realistic scenario, in which you want to create appointments based on data in an Access table of project data, use the CreateProjectAppts function (it can also be run from the mcrCreateProjectAppts macro). This function selects records in tblContactsWithProjects that have a last meeting date older than a month ago, and creates an Outlook appointment for a project meeting for the following Monday for each of those records, writing data from several fields in the Access record to the appointment item:
Public Function CreateProjectAppts() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim fldCalendar As Outlook.Folder appt As Outlook.AppointmentItem dteMonthAgo As Date dteLastMeeting As Date dteNextMonday As Date strProject As String
245
Part II
Set fldCalendar = nms.GetDefaultFolder(olFolderCalendar) Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblContactsWithProjects) dteMonthAgo = DateAdd(m, -1, Date) dteNextMonday = NextMondayTime() With rst Do While Not .EOF
Check whether the last meeting date is older than a month ago.
dteLastMeeting = Nz(![LastMeetingDate]) strProject = Nz(![CurrentProject]) If dteLastMeeting < dteMonthAgo Then
246
FIGURE 8.23 A project meeting appointment created from data in an Access table.
247
Part II
Dim strPriority As String Dim lngPriority As Long Dim strDescription As String Set Set Set Set Set appOutlook = GetObject(, Outlook.Application) nms = appOutlook.GetNamespace(MAPI) fldTasks = nms.GetDefaultFolder(olFolderTasks) dbs = CurrentDb rst = dbs.OpenRecordset(tblTasks)
Check for valid dates, and convert blank dates into 1/1/4501 (that is a blank date in Outlook).
If IsNull(![Start Date]) = True Then dteStartDate = #1/1/4501# Else dteStartDate = Nz(![Start Date]) End If If IsNull(![Due Date]) = True Then dteDueDate = #1/1/4501# Else dteDueDate = Nz(![Due Date]) End If
248
Figure 8.24 shows the exported tasks in the Tasks folder (in Outlook 2007, this folder is now called the To Do List).
249
Part II
FIGURE 8.24 Tasks in the Outlook To Do List exported from an Access table.
To import tasks from your local Outlook Tasks folder into an Access table (tblImportedTasks), use the following function (it can also be run from the mcrImportTasksFromOutlook macro):
Public Function ImportTasksFromOutlook On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim fldTasks As Outlook.Folder tsk As Outlook.TaskItem strTaskName As String dteStartDate As Date dteDueDate As Date strStatus As String lngStatus As Long strPriority As String lngPriority As Long strDescription As String lngPercentComplete As Long itm As Object strSQL As String
Set appOutlook = GetObject(, Outlook.Application) Set nms = appOutlook.GetNamespace(MAPI) Set fldTasks = nms.GetDefaultFolder(olFolderTasks)
Iterate through tasks in the Tasks folder and import them to the Access table.
For Each itm In fldTasks.Items If itm.Class = olTask Then Set tsk = itm
250
With tsk strTaskName = Nz(.Subject) dteStartDate = Nz(.StartDate) dteDueDate = Nz(.DueDate) lngStatus = Nz(.Status) lngPriority = Nz(.Importance) strDescription = Nz(.Body) lngPercentComplete = Nz(.PercentComplete) End With With rst rst.AddNew ![Subject] = strTaskName If dteStartDate <> #1/1/4501# Then ![Start Date] = dteStartDate End If If dteDueDate <> #1/1/4501# Then ![Due Date] = dteDueDate End If Convert Priority number to text for Access strPriority = Switch(lngPriority = 1, _ (1) High, _ lngPriority = 2, (2) Normal, _ lngPriority = 3, (3) Low, _ 0, ) ![Priority] = strPriority
251
Part II
.Update End With End If Next itm rst.Close DoCmd.OpenTable tblImportedTasks ErrorHandlerExit: Exit Function ErrorHandler:
252
For a more realistic scenario, in which you want to create tasks based on data in an Outlook table, see the CreateProjectTasks function (it can be run from the mcrCreateProjectTasks macro). This function creates an Outlook task for each record in tblContactsWithProjects that has not had its supplies replenished for a month or more, and writes data from several fields in the Access record to the task item:
Public Function CreateProjectTasks() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim fldTasks As Outlook.Folder tsk As Outlook.TaskItem dteMonthAgo As Date dteReplenished As Date dteNextMonday As Date strProject As String
Set appOutlook = GetObject(, Outlook.Application) Set nms = appOutlook.GetNamespace(MAPI) Set fldTasks = nms.GetDefaultFolder(olFolderTasks) Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblContactsWithProjects) dteMonthAgo = DateAdd(m, -1, Date) dteNextMonday = NextMonday() With rst Do While Not .EOF
Check whether supplies were last replenished more than a month ago.
dteReplenished = Nz(![SuppliesReplenished]) strProject = Nz(![CurrentProject]) If dteReplenished < dteMonthAgo Then
253
Part II
254
CROSS-REF
If you have a set of linked Access tables of contact data, and you want to keep the data in the tables synchronized with matching Outlook contact items, you need more than a simple export. See Chapter 11 for a discussion of two-way synchronizing between Access and Outlook contacts.
To export data from an Access flat-file contacts table (such as tblContactsToExport) to Outlook contacts in a custom Contacts folder called Contacts from Access, use the function listed as follows (it can also be run from the mcrExportFlatFileContactsToOutlook macro):
Public Function ExportFlatFileContactsToOutlook() On Error GoTo ErrorHandler Set appOutlook = GetObject(, Outlook.Application) Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim lngContactID As Long lngContactCount As Long fld As Outlook.Folder fldContacts As Outlook.Folder conNew As Outlook.ContactItem conTest As Outlook.ContactItem strFullName As String strFirstName As String strLastName As String strBusinessPhone As String strMobilePhone As String strFaxNumber As String strNotes As String strJobTitle As String strStreetAddress As String strCity As String strStateProv As String strPostalCode As String strCountry As String strCompanyName As String strEMail As String strSalutation As String
Use the following line to export to the default local Contacts folder:
Set fldContacts = nms.GetDefaultFolder(olFolderTasks) On Error Resume Next
Use the following lines to export to a custom Contacts folder, creating it if necessary. If setting a reference to the folder fails, the folder will be created.
255
Part II
Set fld = nms.Folders(Personal Folders) Set fldContacts = fld.Folders(Contacts from Access) If fldContacts Is Nothing Then Set fldContacts = _ fld.Folders.Add(Contacts from Access, _ olFolderContacts) End If On Error GoTo ErrorHandler lngContactCount = 0 Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblContactsToExport) With rst Do While Not .EOF
Check whether there already is an Outlook contact item for this person.
On Error Resume Next Set conTest = fldContacts.Items(strFullName) If conTest.FullName <> strFullName Then
256
& IIf(Nz(![WorkExtension]) <> , x _ & ![WorkExtension], ) strMobilePhone = Nz(![MobilePhone]) strFaxNumber = Nz(![FaxNumber]) strNotes = Nz(![Notes]) strStreetAddress = Nz(![StreetAddress]) strCity = Nz(![City]) strStateProv = Nz(![StateOrProvince]) strPostalCode = Nz(![PostalCode]) strCountry = Nz(![Country])
257
Part II
Figure 8.27 shows some of the contacts exported to the custom Contacts from Access folder.
FIGURE 8.27 Contacts in a custom Outlook folder exported from Access, in the new Business Card view.
258
To import contacts from your local Outlook Contacts folder into an Access table (tblImportedContacts), use the following function (it can also be run from the mcrImportContactsFromOutlook macro):
Public Function ImportContactsFromOutlook() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim lngContactCount As Long fld As Outlook.Folder fldContacts As Outlook.Folder con As Outlook.ContactItem strFullName As String strFirstName As String strLastName As String strBusinessPhone As String strHomePhone As String strMobilePhone As String strFaxNumber As String strNotes As String strJobTitle As String strWorkAddress As String strWorkCity As String strWorkStateProv As String strWorkPostalCode As String strWorkCountry As String strHomeAddress As String strHomeCity As String strHomeStateProv As String strHomePostalCode As String strHomeCountry As String strCompanyName As String strEMail As String strSalutation As String itm As Object strSQL As String strWebSite As String
Use the following line to import from the default local Contacts folder:
Set fldContacts = nms.GetDefaultFolder(olFolderTasks) On Error Resume Next
259
Part II
Use the following lines to set a reference to a custom Contacts folder, creating it if necessary. If setting a reference to the folder fails, the folder will be created.
Set fld = nms.Folders(Personal Folders) Set fldContacts = fld.Folders(Contacts to Export) If fldContacts Is Nothing Then Set fldContacts = _ fld.Folders.Add(Contacts to Export, _ olFolderContacts) End If On Error GoTo ErrorHandler
Iterate through contacts in selected Contacts folder and import them to the Access table.
For Each itm In fldContacts.Items If itm.Class = olContact Then Set con = itm With con strFirstName = Nz(.FirstName) strLastName = Nz(.LastName) strJobTitle = Nz(.JobTitle) strWorkAddress = Nz(.BusinessAddressStreet) strWorkCity = Nz(.BusinessAddressCity) strWorkStateProv = Nz(.BusinessAddressState) strWorkPostalCode = _ Nz(.BusinessAddressPostalCode) strWorkCountry = Nz(.BusinessAddressCountry) strHomeAddress = Nz(.HomeAddress) strHomeCity = Nz(.HomeAddressCity) strHomeStateProv = Nz(.HomeAddressState) strHomePostalCode = Nz(.HomeAddressPostalCode) strHomeCountry = Nz(.HomeAddressCountry) strCompanyName = Nz(.CompanyName) strEMail = Nz(.Email1Address) strBusinessPhone = _ Nz(.BusinessTelephoneNumber) strFaxNumber = Nz(.BusinessFaxNumber) strMobilePhone = Nz(.MobileTelephoneNumber) strSalutation = Nz(.NickName)
260
strWebSite = Nz(.WebPage) strNotes = Nz(.Body) .Close (olSave) End With With rst rst.AddNew ![CompanyName] = strCompanyName ![FirstName] = strFirstName ![LastName] = strLastName ![Salutation] = strSalutation ![EmailName] = strEMail ![JobTitle] = strJobTitle ![WorkPhone] = strBusinessPhone ![MobilePhone] = strMobilePhone ![FaxNumber] = strFaxNumber ![Notes] = strNotes ![WorkAddress] = strWorkAddress ![WorkCity] = strWorkCity ![WorkStateOrProvince] = strWorkStateProv ![WorkPostalCode] = strWorkPostalCode ![WorkCountry] = strWorkCountry ![HomeAddress] = strHomeAddress ![HomeCity] = strHomeCity ![HomeStateOrProvince] = strHomeStateProv ![HomePostalCode] = strHomePostalCode ![HomeCountry] = strHomeCountry ![WorkPhone] = strHomePhone ![WebSite] = strWebSite .Update End With lngContactCount = lngContactCount + 1 End If Next itm rst.Close If lngContactCount = 0 Then MsgBox No contacts to import from Outlook Else MsgBox lngContactCount _ & contact(s) imported from Outlook End If ErrorHandlerExit: Exit Function If Err.Number = 429 Then Set appOutlook = CreateObject(Outlook.Application) Resume Next
261
Part II
Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Function
CROSS-REF
When working with contacts, the ideal situation would be to maintain your most complete information in a set of linked Access tables, and synchronize them with Outlook contacts that would display most of the information in an easy-to-use interface. Chapter 11 covers synchronizing linked Access contact tables with a folder of Outlook contacts.
Summary
This chapter has described the components of the Outlook object model you need to understand in order to work with Outlook objects and has given you examples of exporting Access data to Outlook and importing Outlook data into Access (or linking to it), as well as creating new Outlook items based on data changes in Access tables. While there are both advantages and disadvantages to storing contacts in Access and Outlook, I recommend exporting tasks and appointments from Access tables to Outlook, so they can be managed in its superior interface. For contacts, the decision depends on which is more important to you: the convenience and attractive interface of Outlook contacts, or the more sophisticated Access relational database interface, allowing you to set up one-to-many links between companies and contacts, or contacts and phones or IDs.
262
n the previous chapters I discussed creating Word documents, Excel worksheets, and various types of Outlook items using VBA Automation code. But these arent the only types of documents you need to work with sometimes you need to create a plain text document, or import data from one into an Access table. But, before you can work with these documents, you need to work with folders. This chapter covers writing code that works with Windows Explorer folders and text files, using several different methods.
IN THIS CHAPTER
Creating Windows folders Creating FolderPicker and FilePicker dialogs using the Office FileDialog object Writing data to text files using the FileSystemObject, legacy VB statements, and ADO Reading data from text files using the FileSystemObject, legacy VB statements, and ADO Loading files into Attachment fields and saving attachments to files
As Access versions have progressed, the available tools for working with files or folders have advanced. In Access 1.0, the notoriously cryptic callback function was the only way to get a list of files to display in a combo box or listbox. By Windows 95, the CommonDialog control was a possibility, at least if you had the Developer edition of Office. But the CommonDialog control was plagued with version problems if you put one version of it on a form, and sent the database to another person who had a different version of the control, the other person would just get the mysterious message There is no object in this control on opening the form with the CommonDialog control.
CROSS-REF
Another advance came with the Scripting Runtime library, which provided a FileSystemObject object (yes, thats two objects!) that is very useful for finding, working with, or creating files and folders in code. However, it doesnt offer a dialog-type interface for selecting files or folders. The next advance came with Office XP, which introduced a new tool for working with files and folders. The FileDialog object (part of the Office
263
Part II
object model) lets you pop up a dialog for selecting a file or a folder, with several dialog type options. This dialog lets users easily select a file or folder, whose name can then be used in code for further operations. There is no longer any reason to use a Callback function or the CommonDialog control for working with files and folders, so this chapter covers using the FileSystemObject and the FileDialog object for working with files and folders, and components of the ADO object model and legacy VB statements for working with text files.
CROSS-REF
See the Word Export.accdb sample databases main menu for command buttons with code for selecting the Documents and Templates paths.
To work with Windows folders, you have two options: the Office FileDialog object, or the FileSystemObject. These two methods are discussed in the following sections.
NOTE
264
FIGURE 9.1 A main menu with a Backup button and button/textbox controls for selecting an Input Documents path and an Output Documents path for use in the database.
The Input Documents Path and Output Documents Path buttons run procedures that create a FileDialog object. FileDialog objects can be created as a File Picker, or a Folder Picker dialog; in this case the msoFileDialogFolderPicker named constant is used when creating the dialog, to make it a Folder Picker dialog:
Private Sub cmdInputDocsPath_Click() On Error GoTo ErrorHandler
265
Part II
Else Debug.Print User pressed Cancel End If End With On Error Resume Next DoCmd.RunCommand acCmdSaveRecord ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub Private Sub cmdOutputDocsPath_Click() On Error GoTo ErrorHandler
266
ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
In the cmdInputDocsPath_Click event procedure, the GetInputDocsPath() function is used to get the saved Input Documents path value from tblInfo (if there is one); otherwise the default Documents folder is opened. The user can select another path, or accept the default path; the value selected from the dialog is saved to the txtInputDocsPath textbox on the form, which is bound to the InputDocsPath field in tblInfo. The cmdOutputDocsPath_Click event procedure stores the selected template path to txtOutputDocsPath, which is stored in the OutputDocsPath field in tblInfo.
NOTE
I use a tblInfo table in most of my databases to store data that is needed throughout the database, such as path information. Although you can use global variables for this purpose, they wont persist from one session to another, and it isnt easy to examine their values, so I prefer to store these values in a table.
The custom Input and Output Documents paths stored in tblInfo are picked up wherever needed in the database, using the GetInputDocsPath() and GetOutputDocsPath() functions, listed next:
Public Function GetInputDocsPath() As String On Error GoTo ErrorHandler Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblInfo) rst.MoveFirst strPath = Nz(rst![InputDocsPath])
267
Part II
End Function Public Function GetOutputDocsPath() As String On Error GoTo ErrorHandler Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblInfo) rst.MoveFirst strPath = Nz(rst![OutputDocsPath])
Figure 9.2 shows the Folder Picker dialog for selecting a custom Output Documents path. To use the FileDialog object in your code, you need to set a reference to the Office object library; that reference is not set by default in a newly created Access 2007 database. Figure 9.3 shows the Office 12.0 reference being checked in the References dialog for the Files and Folders database.
268
269
Part II
The FileDialog object has several useful methods and properties: n Filters.Add Lets you specify the filter(s) for displaying files, such as
fd.Filters.Add Word documents, *.doc
n InitialFileName The default file name, for a File Picker dialog; the default path for a Folder Picker dialog n ButtonName The button caption (in case you want something other than Select). n DialogType A selection of File Picker, Folder Picker, Open, or Save As dialog type (from the MsoFileDialogType enum, which can be viewed in the Object Browser). n InitialView Lets you set the view for the dialog (Details, Large Icons, Preview, and so on). Look at the MsoFileDialogView enum in the Object Browser for the full selection. n AllowMultiSelect If set to True, lets users select multiple files in the dialog (for File Picker dialogs only). Figure 9.4 shows the MsoFileDialogView enum, with all the options for setting the view for the dialog.
270
The FileSystemObject
The FileSystemObject library provides another way to work with Windows folders (and files). To use the components of the object library in a database, you need to set a reference, in this case to the Scripting Runtime library, as shown in Figure 9.5. If you dont see the Microsoft Scripting Runtime selection in the References dialog, you can get this library by downloading Microsoft Windows Script 5.6 (or whatever is the current version), plus the Microsoft Windows Script 5.6 Documentation files from the Microsoft Windows Scripting Downloads page at http://www.microsoft.com/downloads/details.aspx? familyid=01592C48-207D-4BE1-8A76-1C4099D7BBB9&displaylang=en.
The Help file shown in Figure 9.6 (script56.chm) is a compiled HTML Help file. Youll find it very helpful, because it includes a Help book with full information on the components of the FileSystemObject, plus useful code samples.
271
Part II
n my opinion, Access Help reached its highest point in Access 3.1, when it was provided in the form of a Windows Help file, every bit of which was written specifically for the current version of Access, and was available without an Internet connection. In successive versions of Access, Help moved to the HTML format, which introduced the possibility of retrieving Help topics that were not relevant to Access (say from the MSDN Library that came with VB 6.0), because properties, methods, controls, and other objects may occur in many different Microsoft applications, although they may not (usually dont) work exactly the same. If you are trying to determine which properties of a Tab control will work on an Access form, it isnt much help if the Help topic you find is for a Tab control on an Office UserForm or a VB form. Access 2007 Help is even less useful; it searches all of Office online (at least, if you are connected to the Internet; otherwise, you wont get any help at all), using a shamefully ineffective search engine. I entered Tab control into the search box in the Access Help window and got the list of topics shown in the following figure. Not a single one of them is relevant. One might think that Access doesnt support Tab controls, but of course that is not the case.
272
continued
273
Part II
continued
274
continued
275
Part II
continued
276
277
Part II
NOTE
If you are running the backup code in Windows Vista, you may be unable to back up databases in certain folders, because of Vista security restrictions. This is a Vista issue, not a problem with the database, as you can backup the database after moving it to another folder with lower security.
278
Though it is much easier to back up an Access database in Access 2007 than in previous versions, the new Back Up Database selection on the Manage menu defaults to saving the database copy in the same folder as the database itself. If you want to save your backups to another folder (which I prefer, to avoid confusion between databases and their backups), you have to browse for that folder. The BackupDB function in the following basBackup module saves backups to a folder called Backups under the database folder; you can modify the hard-coded path for saving backups as desired if you want to save your backups to another location. If you want to save backups to a Daily Backups folder on drive E, for example, you would replace the lines of code
strBackupPath = Application.CurrentProject.Path _ & \Backups\
with
strBackupPath = E:\Daily Backups\
CROSS-REF
See Chapter 14 for a discussion of an add-in with user-selectable backup options, including selection of the backup folder from a Folder Picker dialog.
The Access 2007 Backup.accdb database contains the table, module, and macros that are used to do the database backups. These database objects can be imported into any Access 2007 database, and the BackupDB function can be run from the mcrBackup macro, or from a button on the main menu. The basBackup module is listed as follows:
Option Explicit Option Compare Database Private Private Private Private Private Private Private Private Private Private Private Private Private Private dbs As DAO.Database fld As Scripting.Folder fso As Scripting.FileSystemObject intReturn As Integer rst As DAO.Recordset strBackupPath As String strCurrentDB As String strDayPrefix As String strDBName As String strDefault As String strFinalSaveName As String strPrompt As String strSaveName As String strTitle As String
279
Part II
280
MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Function Public Function SaveNo() As String On Error GoTo ErrorHandler Dim intDayNo As Integer Dim strNextNo As String
The SaveNo() function creates an incrementing number for the current backup by picking up the latest number stored for todays date from tblBackupInfo and adding 1 to it. The BackupDB procedure backs up the current database, creating a save name from the databases name (picked up from the Name property of the CurrentProject property of the Application object), plus the SaveNo() value and todays date, formatted with dashes. (You can change the date format as desired, so long as you dont use slashes or other characters that are not permitted in file names.) The proposed save name is presented in an InputBox, where it can be edited as desired, such as adding info on specific changes made to the database; it is then saved to a folder called Backups under the current database folder. The GetFolder method of the FileSystemObject is used to reference the Backups folder; if the folder is not found, the functions error handler creates the folder using the CreateFolder method. A record is added to tblBackupInfo with the date and the save number, and finally the
281
Part II
CopyFile method of the FileSystemObject is used to copy the current database to a backup
with the final save name in the Backups folder. The tblBackupInfo table stores dates and incrementing numbers for the backup names. The backups you make on a given day will have a number (starting with 1) and the date, so they dont overwrite each other, and you will know the order in which the backups were created. Figure 9.9 shows the InputBox presented by the BackupDB function; you can accept the proposed save name or edit it as desired.
CROSS-REF
Exporting to (and importing from) comma-delimited and fixed-width text files is covered in Chapter 10.
282
FIGURE 9.10 A form with options to create a text file with information about skipped records using three different methods.
The complete cmdCreateEMails_Click event procedure is listed next; the code first checks that the message subject and body text have been entered on the form and sets a reference to the Outlook Application object, deletes the old text file, if it exists, then sets up a Select Case statement to work with text files differently, according to which option was selected in the Text Type option group:
Private Sub cmdCreateEMails_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim appOutlook As Outlook.Application fso As Scripting.FileSystemObject msg As Outlook.MailItem strBody As String strEMailRecipient strSubject As String strTo As String varItem As Variant strTest As String lngContactID As Long strFullName As String strText As String strCompanyName As String strDocsPath As String strFile As String
283
Part II
blnSomeSkipped As Boolean intTextType As Integer strTitle As String strPrompt As String txt As Scripting.TextStream tstr As ADODB.Stream
Checks passed; proceed to create a message using the selected text output method.
Set appOutlook = GetObject(, Outlook.Application) strFile = strDocsPath & Skipped Records.txt Debug.Print Text file: & strFile On Error Resume Next
284
ADO
Set tstr = New ADODB.Stream tstr.Open strText = Information on progress creating _ & Outlook mail messages tstr.WriteText Data:=strText, Options:=adWriteLine tstr.WriteText Data:=vbCrLf & vbCrLf blnSomeSkipped = False For Each varItem In lst.ItemsSelected
285
Part II
If strTest = Then blnSomeSkipped = True strText = Contact No. & lngContactID _ & ( & strFullName _ & ) skipped; no company name tstr.WriteText Data:=vbCrLf tstr.WriteText Data:=strText, _ Options:=adWriteLine GoTo NextContactADO End If
Has required info; create new mail message and send to contact.
Set msg = appOutlook.CreateItem(olMailItem) With msg .To = strEMailRecipient .Subject = strSubject .Body = strBody .Send End With NextContactADO: Next varItem If blnSomeSkipped = True Then
FSO
Set fso = CreateObject(Scripting.FileSystemObject) Set txt = fso.CreateTextFile(FileName:=strFile, _ overwrite:=True) strText = Information on progress creating _ & Outlook mail messages txt.WriteLine Text:=strText txt.WriteBlankLines Lines:=2 blnSomeSkipped = False For Each varItem In lst.ItemsSelected
286
Has required info; create new mail message and send to contact.
Set msg = appOutlook.CreateItem(olMailItem) With msg .To = strEMailRecipient .Subject = strSubject .Body = strBody .Send End With NextContactFSO: Next varItem
287
Part II
288
If strTest = Then blnSomeSkipped = True strText = Contact No. & lngContactID _ & ( & strFullName _ & ) skipped; no company name Print #1, Print #1, strText GoTo NextContactVB End If
Has required info; create new mail message and send to contact.
Set msg = appOutlook.CreateItem(olMailItem) With msg .To = strEMailRecipient .Subject = strSubject .Body = strBody .Send End With NextContactVB: Next varItem If blnSomeSkipped = True Then
289
Part II
Figure 9.11 shows a typical text file created by the cmdCreateEMails_Click event procedure code (the text file is the same regardless of the method used to create it).
Figure 9.12 shows one of the email messages created by the previous code. As is so often the case with Access, you have a choice of several techniques to use when working with text files in VBA code. Any of the three methods discussed in the next sections can create a text file and write to it; which method you use depends on such factors as your familiarity with the technique, or the need for extra references in the database to support the code. I generally use the FileSystemObject method, partly because I usually have a reference set to the Scripting Runtime library for other purposes and partly because its syntax is the most intuitive. If your database has a reference to the ADO library, but not the Scripting Runtime library, you can use the ADO method to avoid the need for setting an extra reference; if you dont have a reference set to either the ADO or Scripting Runtime libraries, you can use the VB method to avoid setting an extra reference.
290
ADO
The ActiveX Data Objects (ADO) model isnt the best method for working with Access data, but it does offer some extra features that are very handy for working with text files, in particular the Stream object. The ADO case in the cmdCreateEMails_Click event procedure starts by setting a variable to a Stream object in the ADO object model (note that the object model prefix is ADODB), using the New keyword. The next line opens the new Stream object (you cant write to it unless it is open). A string of introductory text to be written to the file is saved to the strText variable and then written to a line in the stream using the WriteText method. To create two blank lines in the text file, WriteText is then used with two vbCrLf constants (representing a CR + LF meaning carriage return plus linefeed antique terminology dating back to the days of , manual typewriters).
NOTE
You need a reference to the ActiveX Data Objects object library to support this code.
The code iterates through the ItemsSelected collection of the lstSelectContacts listbox, checking each record for required fields (Email and CompanyName). For each record that lacks data in one or both of the required fields (and thus wont get an email message), an If . . . Then statement writes a line containing information on which record has been skipped, and why, plus another blank line. At the end of the Case, a final line is written, and the stream is saved to a text file using the SaveToFile method with the adSaveCreateNotExist option, which creates the file if it does not exist.
291
Part II
FileSystemObject
The FileSystemObject object library provides an alternate approach to working with text files, using the TextStream object. The FSO case in the cmdCreateEMails_Click event procedure starts by creating a FileSystemObject variable and then creating a TextStream variable using the CreateTextFile method. A string of introductory text to be written to the file is saved to the strText variable and then written to a line in the text file using the WriteLine method. To create two blank lines in the text file, the WriteBlankLines method is then used with the Lines argument set to 2.
NOTE
You need a reference to the Microsoft Scripting Runtime object library to support this code.
The code iterates through the ItemsSelected collection of the lstSelectContacts listbox, checking each record for required fields (Email and CompanyName). For each record that lacks data in one or both of the required fields (and thus wont get an email message), an If . . . Then statement writes a line containing information on which record has been skipped, and why, plus another blank line. When all the items have been processed, a final line is written to the text file.
VB
The third case uses legacy VB statements from the earliest days of Access; no object model reference is needed. The Open strFile For Output As #1 statement creates and opens the text file for output. A string of introductory text to be written to the file is saved to the strText variable and then written to a line in the text file using the rather unintuitive Print #1 method. To create two blank lines in the text file, two Print #1 lines with no argument are used. The code iterates through the ItemsSelected collection of the lstSelectContacts listbox, checking each record for required fields (Email and CompanyName). For each record that lacks data in one or both of the required fields (and thus wont get an email message), an If . . . Then statement writes another blank line and a line containing information on which record has been skipped. At the end of the case, a final line is written, and the file is closed using the Close #1 statement. This does not delete the text file, just closes it.
292
FIGURE 9.13 A form with a choice of loading data from a text file using three different methods.
293
Part II
The cmdLoadData_Click event procedure that reads data from a text file is simpler than the code that writes data, because two of the three methods (in their own way) can import all the data from a text file without needing to process each line separately. The cmdLoadData_Click procedure starts by setting a reference to the textbox that holds the name of the selected text file, and checks that there is a file name in the box. If there is a file name, it is saved to a variable, and then a Select Case statement is set up to process the import separately depending on whether the user selects ADO, FSO, or VB in an option group:
Private Sub cmdLoadData_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim fso As Scripting.FileSystemObject strText As String strFile As String intTextType As Integer strTitle As String strPrompt As String txt As Scripting.TextStream stm As ADODB.Stream txtData As Access.TextBox strTextFile As String strData As String strLine As String
Set txtData = Me![txtSelectedTextFile] strTextFile = Nz(txtData.Value) If strTextFile = Then strTitle = No text file selected strPrompt = Please select a text file MsgBox prompt:=strPrompt, Buttons:=vbExclamation _ + vbOKOnly, title:=strTitle GoTo ErrorHandlerExit Else Debug.Print Text file: & strTextFile End If intTextType = Nz(Me![fraTextType].Value, 2) Select Case intTextType Case 1
ADO
Set stm = New ADODB.Stream With stm .Charset = ASCII
294
FSO
Set fso = CreateObject(Scripting.FileSystemObject) Set txt = fso.OpenTextFile(FileName:=strTextFile, _ IOMode:=ForReading)
Close file.
txt.Close Case 3
Close file.
Close #2 End Select
295
Part II
The next sections describe the specific techniques used in the code for the ADO, FSO, and VB methods for loading data from a selected text file.
ADO
For the ADO option, a Stream variable is set, using the New keyword, the Stream object is opened, and the selected text file is loaded, using the LoadFromFile method. The ReadText method is used with the adReadAll named constant as its argument to read in all the text from the text file, and that data is then written to the strData variable. Finally, the Stream object is closed.
FSO
For the FSO (FileSystemObject) option, first a variable is set to the FileSystemObject, and then the text file is opened with the OpenTextFile method of the FileSystemObject, with the ForReading value for the IOMode argument. All of the data from the text file is read using the ReadAll method, and it is saved to the strData variable. Finally, the text file is closed.
VB
The last method, VB, uses the Open strTextFileForInput As #2 statement to open the file, and sets up a Do While . . . Loop structure to process all of the lines in the text file, saving data from each line to the strData variable, incrementing it line by line with a vbCrLf constant in between lines. When all the lines have been processed, the text file is closed. Finally, however the data has been accumulated, the strData variable is written to the txtImportedText textbox, as shown in Figure 9.15.
296
FIGURE 9.15 A form with data loaded from a text file, using the FileSystemObject method.
Previous versions of Access had an OLE Object field data type, which only supported certain types of objects and was quite cumbersome to use (not to mention causing terrible database bloat). By contrast, in Access 2007, it is quite easy to store files of any type in a field of the new Attachment data type, and the attachments are automatically compressed to save database space. The Contacts form, frmContactsWithAttachments (opened from the Browse Contacts button on the main menu) has such a field. If an attachment has already been added to the field, it shows as an icon (at least, if it is of a type recognized by Office); Figure 9.16 shows a record with a Word 2007 document attachment.
NOTE
The attachment icons differ according to the Office version of the stored attachment file. A Word documents icon has a Word document with a W image over the upper-left corner; the style of the W differs for Word 97-2003 (.doc) or Word 2007 (.docx) documents. For a Word 2007 document, the W is similar to the one displayed in a Word 2007 documents taskbar icon; for Word 97-2003 documents, the W is the older style W that was used as the Word icon in Office 97. Other Office documents also have different icons depending on their version.
297
Part II
I discussed using the FileSystemObject (Scripting Runtime library) for working with folders in an earlier section of this chapter; you can also use the FileSystemObject to work with files in a folder, or create files. One possible use is to select a file from a folder to store in a new Attachment data type field in an Access table.
FIGURE 9.16 A form with an attachment field, showing a stored Word 2007 document.
To add an attachment, just double-click the field; this opens the Attachments dialog (shown in Figure 9.17), where you can view existing attachments (multiple attachments can be stored in one Attachment field), or add a new one by clicking the Add button.
FIGURE 9.17 The Attachment dialog, showing a stored Word 2007 document.
The Add button in the Attachments dialog (shown in Figure 9.17) opens a Choose File dialog (shown in Figure 9.18) where you can select a file to store in the Attachment field.
298
NOTE
If you store multiple files in an Attachment field, only the first files icon will be displayed in the form control.
Though it is easy enough to store an attachment or two manually, if you have a folder full of attachments that need to be stored in hundreds of records, it is easier to use VBA code to store the attachments, or to extract attachments and save them to a folder. As an example, suppose you have a folder containing numerous Word documents and Excel worksheets (both in Office 2007 and earlier formats) related to contacts. Each document name starts with Contact ID and a number, which corresponds to the ContactID field in tblContacts in the sample database.
The LoadAttachments procedure listed next iterates through the documents in the folder selected by the Input Documents Path button on the main menu, and for any document that starts with Contact ID saves the document to the corresponding contact records File field (this field is of the Attachment data type). An Attachment field can contain multiple attachments, and the collection of attachments is represented in VBA code as a separate recordset of attachments belonging to a record in a table. Using a Recordset2 object (new to Access 2007) to work with the attachments lets you use the new LoadFromFile and SaveToFile methods to work with the attachments. Figure 9.19 shows a folder with Contact ID documents of various types for loading as attachments.
299
Part II
FIGURE 9.19 A folder with some Contact ID documents, for creating attachments.
In the LoadAttachments function listed as follows, declaring the rstAttachments variable as a Recordset2 object (instead of a Recordset object) allows use of the new LoadFromFile and SaveToFile methods, which I use to load files into Attachment fields, or save files from Attachment fields:
Public Function LoadAttachments() On Error GoTo ErrorHandler Dim intSpace As Integer Dim strTest As String Dim strSearch As String strDocsPath = GetInputDocsPath() Set fso = CreateObject(Scripting.FileSystemObject) Set fld = fso.GetFolder(strDocsPath) Set dbs = CurrentDb Set rstTable = dbs.OpenRecordset(tblContacts, dbOpenDynaset) For Each fil In fld.Files strFile = fil.Name Debug.Print File name: & strFile Debug.Print File type: & fil.Type
300
Extract Contact ID from file name, using Mid and InStr functions to start at the beginning of the number and end before the space following the number, if there is one.
strTest = Mid(String:=strFile, Start:=12, Length:=3) intSpace = InStr(strTest, ) If intSpace > 0 lngContactID Start:=1, Else lngContactID End If Then = CLng(Mid(String:=strTest, _ Length:=intSpace - 1)) = CLng(strTest)
strSearch = [ContactID] = & lngContactID Debug.Print Search string: & strSearch strFileAndPath = strDocsPath & strFile
Create recordset of attachments for this record, using the new Recordset2 type recordset.
Set rstAttachments = _ rstTable.Fields(File).Value
Turn off the error handler to prevent errors if the code attempts to add the same file twice; in this case the Attachments recordset wont be updated.
On Error Resume Next With rstAttachments .AddNew .Fields(FileData).LoadFromFile _ (strFileAndPath) .Update .Close End With rstTable.Update Debug.Print Added & strFileAndPath _ & as attachment to Contact ID _ & lngContactID; s record End If
301
Part II
Open the Contacts form to see the attachments that have been loaded.
DoCmd.OpenForm FormName:=frmContacts ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description End Function
Figure 9.20 shows the main menu of the sample database, Files and Folders.accdb, with the Load Attachments option selected; this selection calls the LoadAttachments function.
FIGURE 9.20 The main menu, with an Attachments button with two options, for loading or saving attachments.
302
collection of a record and saves each one to a file in the Output Documents folder selected on the main menu:
Public Function SaveAttachments() On Error GoTo ErrorHandler Dim intSpace As Integer Dim strTest As String Dim strSearch As String strDocsPath = GetOutputDocsPath() Debug.Print Output docs path: & strDocsPath Set fso = CreateObject(Scripting.FileSystemObject) Set fld = fso.GetFolder(strDocsPath) Set dbs = CurrentDb Set rstTable = dbs.OpenRecordset(tblContacts) Do While Not rstTable.EOF
Turn off error handler to prevent errors if the file already exists in the folder.
On Error Resume Next .Fields(FileData).SaveToFile strFileAndPath .MoveNext Loop .Close End With rstTable.MoveNext Loop rstTable.Close strPrompt = All new attachments saved to _ & strDocsPath & folder strTitle = Done! MsgBox strPrompt, vbOKOnly + vbInformation, strTitle
303
Part II
ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Function
NEW FEATURE
In Access 2007, it is much easier to save a database to an older format, as well as saving a copy of the database in the current format (the long awaited Database Save As); the three choices are right on the Save As submenu of the Office menu, as shown in Figure 9-21.
Summary
In this chapter you learned how to use the Office FileDialog object to allow easy selection of a folder for loading or saving documents. In addition, you learned how to use a variety of techniques for creating text files, saving data to them, and loading data from them to Access tables, using ADO, the FileSystemObject, and legacy VB statements, and also how to save attachments from files to the new Attachment field (or extract attachments from such fields and save them as files), using VBA code. These techniques will allow you to work with text files in a variety of ways, to add extra functionality to your databases, over and above working with Office documents.
304
n the previous chapter, you learned how to work with text files, using one old method and two new ones. For some types of text files, you can also use methods of the Access Application object for importing and exporting text files when working with data in VBA code. The TransferText method has been used to import data from (or export data to) comma-delimited or fixed-width files since the early days of Access, and it is still useful in Access 2007, when you are working with files in these formats. In Office XP, the TransferText method was updated to also export to and import from HTML files. Typically, comma-delimited or fixed-width files are produced by mainframe computers, and you may need to import these files into your Access tables or export data from Access tables to comma-delimited or fixed-width files for import into mainframe applications. Additionally, you can use these formats to export data to or import data from other applications whose formats arent directly supported by Access. If you have data in Excel or Lotus spreadsheets, you can use the
TransferSpreadsheet method to import data from them or export data to spreadsheets. And the TransferDatabase method can be used to
IN THIS CHAPTER
Importing and exporting comma-delimited and fixedwidth text files Importing and exporting dBASE, Paradox, and Lotus 1-2-3 files Importing and exporting XML and HTML files Emailing exported text files
transfer data between Access tables and legacy databases or spreadsheets. These three methods dont have the power or flexibility of Automation code, which can iterate through the records in a table, perhaps using a filtered recordset, and write data from specific fields to a worksheet. But if you need to import all the data from a Lotus worksheet or a dBASE database, so you can work with it in Access, these methods come in handy.
305
Part II
These days, it is not likely that you would want to export Access data to a Lotus spreadsheet or a dBASE database, but there is still a need to export comma-delimited or fixed-width text files, so the TransferText method is also useful for exporting data from Access to text files.
2. Next, in the Get External Data - Text File dialog box (shown in Figure 10.2), use the Browse button to select the text file to import (Jobs 02-Jul-2006.txt in the example).
306
10
3. After selecting the file, clicking OK on the Get External Data - Text File dialog box opens the Import Text Wizard (Figure 10.3), with a selection of Delimited or Fixed Width; generally Access pre-selects the correct option.
307
Part II
4. Clicking Next brings up a screen where you can adjust the column widths, as shown in Figure 10.4.
5. The next screen, shown in Figure 10.5, lets you specify field names if they are lacking in the text file (this is often the case with files exported from mainframes). The figure shows the first field (originally named ProductNumber, and given a default field name of Field1 by the wizard) being renamed with its original name. You can also change the data type of the field, if necessary; for example, making a Text field containing numeric data a Long Integer or Currency field, or a Text field containing date/time data a Date/Time field. 6. The screen in Figure 10.6 lets you add, select, or not specify a primary key; in this case, because the data will be appended to a table that has an AutoNumber field, the No primary key option is correct. 7. You will next see a screen that lets you enter the name of the target Access table.
308
10
309
Part II
You have now supplied all the information needed to set up the specification; the next steps, where you save the specification, are new to Access 2007.
NEW FEATURE
Clicking the Finish button opens a new Save Import Steps screen (shown in Figure 10.7). If you check the Save import steps checkbox, more controls appear on the dialog, where you can enter the name and description of the saved import specification, and even create an Outlook task to run it automatically at a specified interval.
Clicking the Save Import button saves the import specification. This allows you to select the saved specification and run it in the future from the Saved Imports button on the External Data tab of the Ribbon (shown in Figure 10.1), which saves a lot of time compared with going through all the steps of the wizard each time you want to do the import.
CAUTION
Unfortunately, Access 2007 VBA code doesnt recognize saved specifications. This feature worked for several previous versions, but at present it is broken, so we must wait for a patch or service pack to fix it. For now, only code that avoids using specifications will work. (You can import from, or export to, a comma-delimited file without a specification, but not a fixedwidth file.)
The Manage Data Tasks screen is shown in Figure 10.8. It has two tabs, one for saved imports and the other for saved exports. On each tab, you can select a saved specification to run.
310
10
FIGURE 10.8 The Manage Data Tasks dialog, where you can select a saved import or export specification.
NOTE
Sometimes the wizard doesnt recognize that the first line of a text file contains the field names. In that case, you need to give the fields appropriate names, and then delete the first row, with the field names as data, after the import is finished (you will probably get some Type Conversion errors in the Import Errors table for that line). Figure 10.9 shows the table of imported jobs data, and the Import Errors table with errors on the first row containing the field names.
FIGURE 10.9 A table of imported text data, with field names in the first data row, and an Import Errors table.
311
Part II
FIGURE 10.10 The main menu of the External Data sample database.
If a text file was previously selected, its name is displayed in the textbox to the right of the Source Text File button.
2. You can use the selected file (if one is listed), or you can select another by clicking the Source Text File button, which opens a File Picker dialog box. The File Picker dialog box is filtered to display either comma-delimited (.csv) or fixed-width (.txt) files, according to the option selected in the Import Text Type option group. In Figure 10.12, Ive selected a comma-delimited text file for import.
312
10
FIGURE 10.11 The Import Job Data from Text File form, as initially opened.
313
Part II
3. After selecting a text file, click the Inspect New Jobs from Text File button to import data from the text file to a temporary table, tblNewJobs.
The code running from this button sets the table as the source object of the Jobs to Inspect subform, so you can see the new records before actually appending them to the tblJobs table, as shown in Figure 10.13.
FIGURE 10.13 The new jobs imported from a comma-delimited text file.
4. After inspecting the new data, you can either discard the data or import it into the tblJobs table.
n Clicking the Clear Imported Jobs Data button discards the data (it is not added to tblJobs). n Clicking the Save New Jobs to Table button runs an append query that adds the new Jobs data to tblJobs. The code does some data type conversion (as shown in Figure 10.14), because all the fields in the text file are text fields.
314
10
FIGURE 10.14 An append query that does some data type conversion before appending the newly imported jobs to the main tblJobs table.
If you clicked the Save New Jobs to Table button, the new records that were displayed on the form were added to tblJobs. The relevant procedures from the form code module are listed next. The Clear Imported Jobs command buttons event procedure clears the datasheet, calls the SaveTextFile Sub procedure, and clears the previously selected file name from the textbox:
Private Sub cmdClearData_Click() On Error Resume Next Me![subNewJobs].SourceObject = Call SaveTextFile() Me![txtSelectedTextFile].Value = End Sub
NOTE
Importing data from fixed-width text files has become much more difficult in Access 2007 than in previous versions of Access. Specifications now store the file name internally, and thus you must create a separate specification for each text file you want to create. This means that the flexible method I use, where you can select the file to import, will not work. I recommend sticking to comma-delimited file imports, if possible.
315
Part II
The Inspect New Jobs from Text File command buttons event procedure first checks that a text file has been selected and then sets variables with the name of the target table for importing and the import type. Next, a Select Case statement handles the two types of import (comma-delimited and fixed-width) separately, using the TransferText statement with different arguments:
Private Sub cmdInspectJobs_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim strText As String strTitle As String strPrompt As String txtData As Access.TextBox strTextFile As String strTable As String strSpec As String
Set txtData = Me![txtSelectedTextFile] strTextFile = Nz(txtData.Value) If strTextFile = Then strTitle = No text file selected strPrompt = Please select a text file MsgBox prompt:=strPrompt, Buttons:=vbExclamation _ + vbOKOnly, title:=strTitle GoTo ErrorHandlerExit Else Debug.Print Text file: & strTextFile End If strTable = tblNewJobs intTextType = Nz(Me![fraTextType].Value, 1) Select Case intTextType Case 1
Comma-delimited:
DoCmd.TransferText transfertype:=acImportDelim, _ TableName:=strTable, _ FileName:=strTextFile, _ hasfieldnames:=True Case 2
Fixed-width
strSpec = Import-Jobs 13-Aug-2006 New style syntax
316
10
Application.CurrentProject.ImportExportSpecifications(strSpec). Execute Old style syntax causes error DoCmd.TransferText transfertype:=acImportFixed, _ specificationname:=strSpec, _ TableName:=strTable, _ FileName:=strTextFile, _ hasfieldnames:=True End Select
The Save New Jobs to Table command buttons event procedure runs an append query that adds the selected jobs to tblNewJobs:
Private Sub cmdSaveJobs_Click() On Error GoTo ErrorHandler DoCmd.SetWarnings False DoCmd.OpenQuery qappNewJobs strTitle = Jobs imported strPrompt = New jobs imported into tblJobs from _ & GetTextFile() MsgBox strPrompt, vbInformation + vbOKOnly, strTitle ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
317
Part II
The Source Text File command buttons event procedure opens a File Picker dialog for selecting a text file for importing, filtering for either text or comma-delimited files depending on the option selected on the form:
Private Sub cmdSourceTextFile_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim fd As Office.FileDialog txt As Access.TextBox strPath As String strFilter As String
Save the value to tblInfo; the form cant be bound to that table because the main menu is bound to it, and thus it is locked.
SaveTextFile (strTextFile) Me![txtSelectedTextFile].Value = strTextFile ErrorHandlerExit: Exit Sub
318
10
ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The Import Text Type option groups event procedure first checks that the selected text file is the right type, and clears it if not, then sets up a Select Case statement to process comma-delimited and text files differently, calling the SaveTextFile Sub:
Private Sub fraTextType_AfterUpdate() On Error GoTo ErrorHandler Dim strExt As String
Check that the selected text file is the right type, and clear the file selection if not.
intTextType = Nz(Me![fraTextType].Value, 1) strTextFile = GetTextFile() If Len(strTextFile) > 4 Then strExt = Right(strTextFile, 3) End If Select Case intTextType Case 1
Comma-delimited
If strExt = txt Then SaveTextFile () Me![txtSelectedTextFile].Value = End If Case 2
Fixed-width
If strExt = csv Then SaveTextFile () Me![txtSelectedTextFile].Value = End If Case 3 If strExt <> csv And strExt <> txt Then SaveTextFile () Me![txtSelectedTextFile].Value = End If
319
Part II
End Select ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
NEW FEATURE
FIGURE 10.15
Controls bound to Date fields on an Access 2007 form have a long-awaited feature shown in Figure 10.15: a pop-up calendar to make it easy to select a date.
The Export Job Data to Text File form, with a pop-up date selector.
320
10
1. To open the Export Job Data to Text File form (frmExportTextData), select the Export Data to Text Files option on the main menu and click the button to its left. This form lets you export a range of records filtered by date to either a comma-delimited or fixed-width text file. 2. After you select the From Date and To Date, either by typing them in or using the date selector pop-up, click the Inspect New Jobs to Export button to show the jobs in the selected date range in the Jobs to Inspect (fsubNewJobs) subform, as shown in Figure 10.16. 3. If you dont want to go ahead with the export, use the Clear Jobs to Export button to clear the selected jobs. 4. To proceed with the export, use the Export Jobs to Text File button to run code using the TransferText method to export the selected date range of jobs to a text file of the type selected in the Export Text Type option group. Figure 10.17 shows an exported comma-delimited file opened in Excel (which is the default application for .csv files).
FIGURE 10.16 Inspecting jobs in a given date range to export to a text file.
321
Part II
NOTE
The new method of dealing with specs works well with fixed-width exports, at least so long as you always want to export to the save file name, as I do in the sample code.
The relevant procedures from the form module are listed next. The Clear Jobs to Export buttons event procedure clears the datasheet subform of jobs:
Private Sub cmdClearData_Click() On Error Resume Next Me![subFilteredJobs].SourceObject = End Sub
322
10
The Export Jobs to Text File buttons event procedure sets up a Select Case statement to export the selected jobs, using the TransferText statement with different arguments:
Private Sub cmdExportJobs_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim intTextType As Integer strQuery As String strTextFile As String strTitle As String strPrompt As String
Comma-delimited
strTextFile = GetOutputDocsPath() _ &Filtered Jobs.csv DoCmd.TransferText transfertype:=acExportDelim, _ TableName:=strQuery, _ FileName:=strTextFile, _ hasfieldnames:=True Case 2
Fixed-width
strTextFile = GetOutputDocsPath() _ &Filtered Jobs.txt strSpec = Export@@hyFilteredJobs strTextFile = GetOutputDocsPath() & Filtered Jobs.txt New style syntax Application.CurrentProject.ImportExportSpecifications(strSpec).Ex ecute Old style syntax causes error DoCmd.TransferText transfertype:=acExportFixed, _ TableName:=strQuery, _ FileName:=strTextFile, _ hasfieldnames:=True End Select
323
Part II
strTitle = Exported jobs strPrompt = Exported filtered jobs to & strTextFile MsgBox strPrompt, vbInformation + vbOKOnly, strTitle ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The Inspect New Jobs to Export buttons event procedure sets the form datasheets source object to the subform bound to qryFilteredJobs, to display the selected jobs:
Private Sub cmdInspectNewJobs_Click() On Error Resume Next Me![subFilteredJobs].SourceObject = fsubFilteredJobs End Sub
324
10
1. To open the Import Job Data from Application File (frmImportAppData) form, select the Import Data from App Files option on the main menu and click the button to its left (see Figure 10.10). The Job Data from Application File form is shown in its initial state in Figure 10.18.
FIGURE 10.18 A form for importing data from legacy database and spreadsheet application files.
2. Click the Source File button to select a file of the type selected in the Import File Type option group from a File Picker dialog, as shown in Figure 10.19. 3. As with text files, click the Inspect New Jobs from App File button to import data from the selected file to a temporary table. The code sets that table as the source object of the Jobs to Inspect subform, so you can see the new records before appending them to the tblJobs table, as shown in Figure 10.20.
325
Part II
FIGURE 10.20 Inspecting new job data imported from a dBASE IV database file.
326
10
NOTE
When importing from (or exporting to) dBASE or Paradox formats using the TransferDatabase method, the DatabaseName argument takes the path, not the database name; the database name is set with the Source argument. The path must be set without a final backslash, or else it will cause an error.
4. Use the Clear Imported Jobs Data button to clear the imported data without adding it to tblJobs. 5. Use the Save New Jobs to Table button to append the imported jobs data to tblJobs. Importing a Paradox file is quite similar; only the database type argument is different. Figure 10.21 shows new jobs data imported from a Paradox 4 database.
Curiously, there isnt an enum of named constants for the values used to set the DatabaseType argument of the TransferDatabase method; you have to type in the values. Table 10.1 lists the values you need to use for different versions of dBASE and Paradox.
327
Part II
TABLE 10.1
dBASE III dBASE IV dBASE 5 Paradox 3.x Paradox 4.x Paradox 5.x Paradox 7.x
dBASE III dBASE IV dBASE 5.0 Paradox 3.X Paradox 4.X Paradox 5.X Paradox 7.X
Now that the data is in an Access table, you can proceed to work with it as needed.
TABLE 10.2
328
10
When you inspect new job data imported from a Lotus spreadsheet (as shown in Figure 10.22), the Job Date/Time field looks strange, because the field is created as a Number field. However, when you click the Save Jobs to Table button, the CDate() function in the qappNewJobs append query converts the numeric date value into the correct format before appending the data to tblJobs. This type of tweaking of imported data is often required, to ensure that the data arriving in the target Access table is the correct data type.
FIGURE 10.22 Numeric values in the Job Date/Time field for data imported from a Lotus 1-2-3 spreadsheet.
The relevant procedures from the Import Job Data from Application File form module are listed next. The Clear Imported Jobs Data buttons event procedure clears the datasheet of records, calls the SaveAppFile Sub, clears the textbox of the file name, and deletes the temporary tables of new jobs:
Private Sub cmdClearData_Click() On Error Resume Next Me![subNewJobs].SourceObject = Call SaveAppFile() Me![txtSelectedAppFile].Value =
329
Part II
The Inspect New Jobs from App File buttons event procedure first checks that an application file has been selected, parses out the file path and file name for use in different arguments of the TransferDatabase statement, then sets up a Select Case statement to do the import differently for the three application types:
Private Sub cmdInspectJobs_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim strText As String strTitle As String strPrompt As String txtData As Access.TextBox strAppFile As String strTable As String strSpec As String strDBPath As String strDBName As String strAppFileAndPath As String
Set txtData = Me![txtSelectedAppFile] strAppFileAndPath = Nz(txtData.Value) If strAppFileAndPath = Then strTitle = No application file selected strPrompt = Please select an application file MsgBox prompt:=strPrompt, Buttons:=vbExclamation _ + vbOKOnly, title:=strTitle GoTo ErrorHandlerExit Else
330
10
dBASE
strTable = tblNewJobsDB Debug.Print DB Path: & strDBPath Debug.Print DB Name: & strDBName DoCmd.TransferDatabase transfertype:=acImport, _ databasetype:=dBASE IV, _ databasename:=strDBPath, _ objecttype:=acTable, _ Source:=strDBName, _ Destination:=strTable, _ structureonly:=False
Paradox
strTable = tblNewJobs DoCmd.TransferDatabase transfertype:=acImport, _ databasetype:=Paradox 4.X, _ databasename:=strDBPath, _ objecttype:=acTable, _ Source:=strDBName, _ Destination:=strTable, _ structureonly:=False
Lotus 1-2-3
strTable = tblNewJobs DoCmd.TransferSpreadsheet transfertype:=acImport, _ spreadsheettype:=acSpreadsheetTypeLotusWK3, _ TableName:=strTable, _ FileName:=strAppFileAndPath, _ hasfieldnames:=True
331
Part II
The Save New Jobs to Table buttons event procedure sets up a Select Case statement to run one of three append queries to append the new Jobs data to the tblJobs table:
Private Sub cmdSaveJobs_Click() On Error GoTo ErrorHandler DoCmd.SetWarnings False intFileType = Nz(Me![fraFileType].Value, 1) Select Case intFileType Case 1
dBASE
DoCmd.OpenQuery qappNewJobsDB Case 2
Paradox
DoCmd.OpenQuery qappNewJobsPdox Case 3
Lotus 1-2-3
DoCmd.OpenQuery qappNewJobsLotus End Select
332
10
strTitle = Jobs imported strPrompt = New jobs imported into tblJobs from _ & GetAppFile() MsgBox strPrompt, vbInformation + vbOKOnly, strTitle ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
The Source File buttons event procedure opens a File Picker dialog for selecting an application file for importing, filtering for dBASE, Paradox, or Lotus files depending on the option selected on the form:
Private Sub cmdSourceFile_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim fd As Office.FileDialog txt As Access.TextBox strPath As String strFilter As String
333
Part II
Case 2 .Filters.Add Paradox files, *.db Case 3 .Filters.Add Lotus 1-2-3 files, *.wk3 End Select .InitialView = msoFileDialogViewDetails .InitialFileName = strPath If .Show = -1 Then strAppFile = CStr(fd.SelectedItems.Item(1)) Else Debug.Print User pressed Cancel End If End With txt.Value = strAppFile
The Import File Type option groups After_Update procedure first checks that the selected application file is the right type, and clears it if not, then sets up a Select Case statement to process the three types of application files differently, calling the SaveTextFile Sub:
Private Sub fraFileType_AfterUpdate() On Error GoTo ErrorHandler Dim strExt As String
Check that selected application file is the right type, and clear the file selection if not.
intFileType = Nz(Me![fraFileType].Value, 1) If Len(GetTextFile()) > 4 Then strExt = Right(strAppFile, 3) End If
334
10
dBASE
If strExt <> dbf Then SaveAppFile () Me![txtSelectedAppFile].Value = End If Case 2
Paradox
If strExt <> .db Then SaveAppFile () Me![txtSelectedAppFile].Value = End If Case 3
Lotus 1-2-3
If strExt <> wk3 Then SaveAppFile () Me![txtSelectedAppFile].Value = End If End Select Me![subNewJobs].SourceObject = ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
After importing data from the legacy application file, it is now in an Access table, where you can work with it in the future.
335
Part II
The Clear Jobs to Export button clears the selected records, and the Export Jobs to Application File button does the export to a file of the selected format; this procedure is listed next. Similar to the other export procedures, this procedure sets up a Select Case statement to do the export differently according to the selected application type; the TransferDatabase statement with various arguments:
Private Sub cmdExportJobs_Click() On Error GoTo ErrorHandler Dim intFileType As Integer Dim strQuery As String
336
10
strAppFile As String strTitle As String strPrompt As String strOutputPath As String strDBName As String
dBASE
strDBName = Jobs.dbf strAppFile = strOutputPath & \ & strDBName DoCmd.TransferDatabase transfertype:=acExport, _ databasetype:=dBASE IV, _ databasename:=strOutputPath, _ objecttype:=acTable, _ Source:=strQuery, _ Destination:=strDBName, _ structureonly:=False Case 2
Paradox
strDBName = Jobs.db strAppFile = strOutputPath & \ & strDBName DoCmd.TransferDatabase transfertype:=acExport, _ databasetype:=Paradox 5.X, _ databasename:=strOutputPath, _ objecttype:=acTable, _ Source:=strQuery, _ Destination:=strDBName, _ structureonly:=False Case 3
337
Part II
Lotus 1-2-3
strAppFile = strOutputPath & \Jobs.wk1 DoCmd.TransferSpreadsheet transfertype:=acExport, _ spreadsheettype:=acSpreadsheetTypeLotusWK1, _ TableName:=strQuery, _ FileName:=strAppFile, _ hasfieldnames:=True End Select strTitle = Exported jobs strPrompt = Exported filtered jobs to & strAppFile MsgBox strPrompt, vbInformation + vbOKOnly, strTitle ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
338
10
If you select the XML option, data from the XML file is displayed in the subform, as shown in Figure 10.25.
339
Part II
NOTE
When you import data from an XML file, there is no option for specifying the name of the Access table; it will have the same name as the original data source, possibly with an appended number.
The code for the Inspect New Jobs From HTML File button is listed below:
Private Sub cmdInspectJobs_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim strText As String strTitle As String strPrompt As String txtData As Access.TextBox strAppFile As String strTable As String strSpec As String strHTMLXMLFileAndPath As String strHTMLXMLFile As String strHTMLXMLPath As String
Set txtData = Me![txtSelectedAppFile] strTable = tblNewJobs strHTMLXMLFileAndPath = Nz(txtData.Value) If strHTMLXMLFileAndPath = Then strTitle = No application file selected strPrompt = Please select an application file MsgBox prompt:=strPrompt, Buttons:=vbExclamation _ + vbOKOnly, Title:=strTitle GoTo ErrorHandlerExit Else Parse out the file name; it is needed later in the procedure. strHTMLXMLFile = SplitDBName(strHTMLXMLFileAndPath) Trim off the file extension. strHTMLXMLFile = Mid(strHTMLXMLFile, 1, InStr(1, strHTMLXMLFile, .) @@hy 1) Debug.Print Trimmed file name: & strHTMLXMLFile End If intFileType = Nz(Me![fraFileType].Value, 1) Select Case intFileType
340
10
Case 1 HTML DoCmd.TransferText transfertype:=acImportHTML, _ TableName:=strTable, _ FileName:=strHTMLXMLFileAndPath, _ hasfieldnames:=True Assign the appropriate form as the subforms source object. Me![subNewJobs].SourceObject = fsubNewJobs Case 2 XML ImportXML DataSource:=strHTMLXMLFileAndPath, _ importoptions:=acStructureAndData DoCmd.SetWarnings False There is no argument for specifying the name of the table that is created when an XML file is imported; it comes in as the name stored in the XML file (usually the XML file name), possibly with a number added on. DoCmd.Rename newname:=strTable, objecttype:=acTable, _ oldname:=strHTMLXMLFile Assign the appropriate form as the subforms source object. Me![subNewJobs].SourceObject = fsubNewJobs End Select ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
341
Part II
XML File (frmExportHTMLXMLData) form will open (as shown in Figure 10.26). The form has From Date and To Date textboxes for specifying a date range; clicking the Inspect New Jobs to Export button loads the subform with the records from the selected date range.
FIGURE 10.26 Inspecting the filtered job records to export to an HTML or XML file.
Clicking the Export Jobs to HTML File button (or Export Jobs to XML File; the caption changes with the selection in the Export File Type option group) starts the export. The HTML export is done with the TransferText method with the acExportHTML value for the TransferType argument; the XML export is done with the ExportXML method of the Access Application object. Figure 10.27 shows an exported HTML file opened in Internet Explorer 7. Unfortunately, it is completely unformatted and thus probably wont be very useful.
342
10
The code for clearing old data and inspecting the jobs to export is similar to the code for other export types; only the event procedure for the Export Jobs to HTML/XML File button is listed as follows; it uses a Select Case statement to export the data to either an HTML file (using the TransferText method) or an XML file, using the ExportXML method of the Access Application object:
Private Sub cmdExportJobs_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim intFileType As Integer strQuery As String strTitle As String strPrompt As String strOutputPath As String strFileName As String strFileNameAndPath As String
343
Part II
HTML
strFileName = Jobs.htm strFileNameAndPath = strOutputPath & strFileName DoCmd.TransferText transfertype:=acExportHTML, _ TableName:=strQuery, _ FileName:=strFileNameAndPath, _ hasfieldnames:=True Case 2
XML
strFileName = Jobs.xml strFileNameAndPath = strOutputPath & strFileName ExportXML objecttype:=acExportQuery, _ DataSource:=strQuery, _ datatarget:=strFileNameAndPath End Select strTitle = Exported jobs strPrompt = Exported filtered jobs to & strFileNameAndPath MsgBox strPrompt, vbInformation + vbOKOnly, strTitle ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
If you open an XML file in IE 7, running on Windows Vista, youll see a yellow bar with a security warning. If you click the bar you can select to allow blocked content, as shown in Figure 10.28.
344
10
FIGURE 10.28 A security warning when opening an XML file in Windows Vista.
If you select to allow blocked content, youll get another security warning, shown in Figure 10.29. Finally, the XML file displays (see Figure 10.30), but as source code, not a properly formatted document, so it (like the HTML file) is not very useful.
345
Part II
You can also open an XML file in Excel. After selecting it, you get an Open XML dialog with three options, as shown in Figure 10.31. To see what the formatted XML data looks like, select the As an XML table option.
346
10
If you accept the default option of As an XML table, youll get the message shown in Figure 10.32.
FIGURE 10.32 Creating an XML schema when opening an XML file in Excel.
After accepting this message, the XML file finally opens in Excel, as shown in 10.33, with an extra column called generated indicating the time the file was created.
NOTE NOTE
If you want to export data from Access to Excel, I recommend using the worksheet or comma-delimited format instead of XML; they are much easier to work with, and support older versions of Excel that cant open XML files. You can also use the Save method of an ADO recordset with the adPersistXML named constant as the value of its PersistFormat argument, to produce an XML file, but a file produced using this method also opens as source code.
347
Part II
FIGURE 10.34 A form for selecting contacts and a job file to email to them, with three email messages created from the form.
348
10
Create a new mail message with the job file attachment and send to contact.
Set appOutlook = GetObject(, Outlook.Application) Set msg = appOutlook.CreateItem(olMailItem) With msg .To = strEMailRecipient .Subject = strSubject .Body = strBody If strJobFile <> Then .Attachments.Add strJobFile End If .Display End With
349
Part II
NextContact: Next varItem ErrorHandlerExit: Set appOutlook = Nothing Exit Sub ErrorHandler:
NOTE
You may have contacts that have only an email address, or a phrase like Tech. Support entered as the last name, or contacts with just a first name, or a whole name entered into the LastName field, or sets of contacts who work for the same company, where the company name is entered differently on different contact records. Importing from such contacts can cause problems, such as creating multiple Company records with variations of a company name.
I am planning to upgrade the Synchronizing Contacts database to deal with various types of problem data, and to add some new features; look for an updated version of the database on my Web site, http://www.helenfeddema.com.
Summary
This chapter dealt with exporting to, and importing from, a variety of file formats, ranging from the oldest formats to those so new that they are scarcely useful yet. Text files, both comma-delimited and fixed-width (columnar), have been used for data export and import since the earliest days of computers, and they are still very useful, especially the comma-delimited file format. Files exported to this format can be imported by a great many applications, which makes it very useful for exporting data that is to be imported by an application not directly supported as an Access export type. The reverse is also true: many applications can export their data to a fixed-width or comma-delimited file, from which they can be imported into Access tables. If you have data in ancient dBASE, Paradox, or Lotus files, Access offers options for importing from these files, so you can get your old data into Access tables. Although it isnt likely to be required these days, you can also export data from Access tables to these legacy formats. And finally, the new HTML and XML formats are supported but not very well. These import and export types still have little utility for importing data into Access tables, either because they simply dont work or because they arent really relevant. Hopefully, these file formats will be better supported for Access import and export in future versions of Office.
350
or a long time really, since Office 97, when Outlook was introduced I have wanted to write VBA code to synchronize Access contacts with Outlook contacts. My Access contacts are stored in a set of linked tables, with companies linked to contacts and contacts linked to addresses, phone numbers, and IDs of various sorts, which allows maximum flexibility for entering data and at the same time avoids having to enter the same data in multiple records. Outlook, on the other hand, has a very attractive and convenient interface for entering contact data, but unfortunately stores all contact data in a flat-file MAPI database, with a limited number of fields for addresses, phone numbers, and IDs. Though it isnt difficult to write code to simply import data from Outlook to an Access table, or export data from an Access table to Outlook contacts, if the Access contacts are a set of linked tables, as they should be, the task is much more difficult but not impossible. Live linking is out of the question, because of the difference in structure between a folder of Outlook contacts and a set of linked Access tables, but the contacts can be compared, and data copied from an Outlook contact to an Access contact (or vice versa), using an intermediary flat-file table filled with data from the linked Access tables. This chapter describes the technique I use to first denormalize Access data for comparison with Outlook contacts and then renormalize the updated data in order to write it back to the linked Access tables.
IN THIS CHAPTER
Updating Outlook contacts from Access, and vice versa Copying attachments from Outlook to Access, and vice versa
CROSS-REF
See the Working with Outlook Contacts section in Chapter 8 for information on exchanging data between a single Access contacts table and Outlook contacts.
351
Part II
NOTE
The process of creating a single flat-file table from data in a set of linked tables is called denormalizing; the reverse process writing data from a flat-file table back to a set of linked tables is called renormalizing.
If you encounter a Query too complex message when trying to run a deeply nested query based on multiple tables (this is less of a problem now than with previous versions of Access, but still might happen with extremely complex queries), you can run a make-table query to create a flat-file table based on some of the linked queries and use that table as part of the final query, to reduce its complexity. The techniques I use in this chapter to prepare a single table of Access data for comparison with Outlook contacts can be modified for use anywhere you need to produce a single flatfile table of data from linked Access tables.
NOTE
In Access, my contact-related data is stored in a set of linked tables, as shown in the Relationships diagram (Figure 11.1). The tables are normalized, which means that they are designed so that data of a particular type is stored in only one table, and only the linking ID fields have matching values. The tblCompanyInfo table is linked one-to-many with two tables: tblCompanyIDsPhones and tblContactInfo, because a company can have multiple phone numbers and IDs, and also multiple contacts. tblContactInfo is also linked one-to-many with two tables: tblContactIDsPhones, containing phone numbers and IDs for contacts, and tblContactAddresses, containing addresses.
352
11
FIGURE 11.1 The Relationships diagram for the Synchronizing Contacts database.
Because Outlook only supports a fixed number of addresses and emails (three of each), and a larger (17) but still fixed number of phone numbers, for purposes of synchronizing contact data between Outlook and Access, only the matching addresses, emails, and phone numbers will be synchronized. Practically, this is not likely to leave much data unsynchronized, except in the case of phone numbers.
TIP
For best results when synchronizing data, when entering a phone number or ID in one of the subforms on frmContactInfo, select one of the default selections for addresses, emails, and phone numbers from the drop-down list; they are the only selections that will be synchronized with Outlook contact items.
Figure 11.2 shows a phone number being selected on the Contact Information (frmContactInfo) form.
353
Part II
FIGURE 11.2 Selecting a default phone number type on the Contact Information form.
Of course, you will sometimes need to enter phone numbers that arent on this list of default phone number choices (such as the Coffee Harvest Line number shown in Figure 11.4); you can enter a custom phone or ID description manually as needed, but these phone numbers and IDs wont be synchronized with Outlook.
354
11
Figure 11.3 shows the Contact Addresses tab of the Contact Information form; unless you need to enter data for very wealthy people who have more than three addresses, the standard three choices should be enough.
The Company and Contact Information (frmCompanyInfo) form displays company and contact information so you can easily match up contacts with their companies. Figure 11.4 shows the Company Info tab of this form, with a Company IDs and Phones subform.
355
Part II
FIGURE 11.4 The Company Info tab of the Company and Contact Information form.
Figure 11.5 shows the Contact Info tab, with a Contact IDs and Phones subform.
356
11
FIGURE 11.5 The Contact Info tab of the Company and Contact Information form.
The sample databases main menu (shown in Figure 11.6) has a command button for selecting the Attachments folder path; its event procedure uses the same technique as for similar command buttons in earlier chapters, opening an Office Folder Picker dialog to let you select a folder. In this chapter the selected folder is used to temporarily store files for use as attachments when copying attachments from an Access table record to an Outlook contact or vice versa.
357
Part II
The code for the Attachments Folder Path button (listed next) starts by popping up a Folder Picker dialog for selecting the folder where files to be used as attachments are stored. The selected path is saved to the textbox under the command button:
Private Sub cmdAttachmentsFolderPath_Click() On Error GoTo ErrorHandler
358
11
If .Show = -1 Then txt.Value = CStr(fd.SelectedItems.Item(1)) Else Debug.Print User pressed Cancel End If End With On Error Resume Next DoCmd.RunCommand acCmdSaveRecord ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
NOTE
Outlook contact items have a number of very useful built-in ID fields, which for some inexplicable reason are not displayed on the standard Contact item. The CustomerID field is the one I use to link Outlook contacts to Access records in tblContactInfo (using the key field ContactID). The GovernmentIDNumber field (corresponding to GovernmentID in tblContactInfo) can be used to store a Social Security Number (for the United States) or the equivalent government ID number for other countries. There is also another field useful for storing a company ID: OrganizationalIDNumber, corresponding to CompanyID in tblCompanyInfo. To test synchronizing Contacts data, make a new Contacts folder and copy some (or all) of your contacts to it from your regular Contacts folder; that way, you can experiment with making various changes without messing up your real contact data.
TIP
359
Part II
FIGURE 11.7 Selecting a form for comparing Access and Outlook contacts.
When you select one of these forms to open, a message box, shown in Figure 11.8, pops up.
You will get several other messages as the tables of Access and Outlook data are created, including an Outlook Select Folder dialog for selecting the Outlook Contacts folder to use when synchronizing the Access and Outlook contacts. This dialog is shown in Figure 11.9.
360
11
FIGURE 11.9 An Outlook Select Folder dialog for selecting the Contacts folder for synchronizing.
Set a variable to the Contacts folder to use when synchronizing: Use the following lines to import from the default local Contacts folder.
Set fldContacts = nms.GetDefaultFolder(olFolderContacts) GoTo ImportData
361
Part II
Use the following section of code to allow selection of a custom Contacts folder from the Folder Picker dialog.
SelectContactFolder: Set fldContacts = nms.PickFolder If fldContacts Is Nothing Then strTitle = Select Folder strPrompt = Please select a Contacts folder MsgBox strPrompt, vbExclamation + vbOKOnly, strTitle GoTo SelectContactFolder End If Debug.Print Default item type: & _ fldContacts.DefaultItemType If fldContacts.DefaultItemType <> olContactItem Then MsgBox strPrompt, vbExclamation + vbOKOnly, strTitle GoTo SelectContactFolder End If Debug.Print fldContacts.Items.Count & items in _ & fldContacts.Name & folder
Iterate through contacts in the selected Contacts folder and import them to the Access table, setting each field in the target table with the value of a field in the current contact item:
For Each itm In fldContacts.Items If itm.Class = olContact Then Set con = itm rstTarget.AddNew With con rstTarget![CustomerID] = Nz(.CustomerID) rstTarget![Title] = Nz(.Title) rstTarget![FirstName] = Nz(.FirstName) rstTarget![MiddleName] = Nz(.MiddleName) rstTarget![LastName] = Nz(.LastName) rstTarget![Suffix] = Nz(.Suffix) rstTarget![Nickname] = Nz(.Nickname) rstTarget![CompanyName] = Nz(.CompanyName) rstTarget![Department] = Nz(.Department) rstTarget![JobTitle] = Nz(.JobTitle)
362
11
rstTarget![BusinessAddressStreet] = _ Nz(.BusinessAddressStreet) rstTarget![BusinessAddressPostOfficeBox] = _ Nz(.BusinessAddressPostOfficeBox) rstTarget![BusinessAddressCity] = _ Nz(.BusinessAddressCity) rstTarget![BusinessAddressState] = _ Nz(.BusinessAddressState) rstTarget![BusinessAddressPostalCode] = _ Nz(.BusinessAddressPostalCode) rstTarget![BusinessAddressCountry] = _ Nz(.BusinessAddressCountry) rstTarget![BusinessHomePage] = _ Nz(.BusinessHomePage) rstTarget![FTPSite] = Nz(.FTPSite) rstTarget![HomeAddressStreet] = _ Nz(.HomeAddressStreet) rstTarget![HomeAddressPostOfficeBox] = _ Nz(.HomeAddressPostOfficeBox) rstTarget![HomeAddressCity] = _ Nz(.HomeAddressCity) rstTarget![HomeAddressState] = _ Nz(.HomeAddressState) rstTarget![HomeAddressPostalCode] = _ Nz(.HomeAddressPostalCode) rstTarget![HomeAddressCountry] = _ Nz(.HomeAddressCountry) rstTarget![OtherAddressStreet] = _ Nz(.OtherAddressStreet) rstTarget![OtherAddressPostOfficeBox] = _ Nz(.OtherAddressPostOfficeBox) rstTarget![OtherAddressCity] = _ Nz(.OtherAddressCity) rstTarget![OtherAddressState] = _ Nz(.OtherAddressState) rstTarget![OtherAddressPostalCode] = _ Nz(.OtherAddressPostalCode) rstTarget![OtherAddressCountry] = _ Nz(.OtherAddressCountry) rstTarget![AssistantTelephoneNumber] = _ Nz(.AssistantTelephoneNumber) rstTarget![BusinessFaxNumber] = _ Nz(.BusinessFaxNumber) rstTarget![BusinessTelephoneNumber] = _ Nz(.BusinessTelephoneNumber) rstTarget![Business2TelephoneNumber] = _ Nz(.Business2TelephoneNumber) rstTarget![CallbackTelephoneNumber] = _ Nz(.CallbackTelephoneNumber) rstTarget![CarTelephoneNumber] = _ Nz(.CarTelephoneNumber)
363
Part II
rstTarget![CompanyMainTelephoneNumber] = _ Nz(.CompanyMainTelephoneNumber) rstTarget![HomeFaxNumber] = _ Nz(.HomeFaxNumber) rstTarget![HomeTelephoneNumber] = _ Nz(.HomeTelephoneNumber) rstTarget![Home2TelephoneNumber] = _ Nz(.Home2TelephoneNumber) rstTarget![ISDNNumber] = Nz(.ISDNNumber) rstTarget![MobileTelephoneNumber] = _ Nz(.MobileTelephoneNumber) rstTarget![OtherFaxNumber] = _ Nz(.OtherFaxNumber) rstTarget![OtherTelephoneNumber] = _ Nz(.OtherTelephoneNumber) rstTarget![PagerNumber] = Nz(.PagerNumber) rstTarget![PrimaryTelephoneNumber] = _ Nz(.PrimaryTelephoneNumber) rstTarget![RadioTelephoneNumber] = _ Nz(.RadioTelephoneNumber) rstTarget![TTYTDDTelephoneNumber] = _ Nz(.TTYTDDTelephoneNumber) rstTarget![TelexNumber] = Nz(.TelexNumber) rstTarget![Account] = Nz(.Account) rstTarget![AssistantName] = Nz(.AssistantName)
Use special handling for a date field (a blank date in Outlook is actually a date of 1/1/4501):
If .Birthday <> #1/1/4501# Then rstTarget![Birthday] = .Birthday End If If .Anniversary <> #1/1/4501# Then rstTarget![Anniversary] = .Anniversary End If If .LastModificationTime <> #1/1/4501# Then rstTarget![LastUpdated] = _ .LastModificationTime End If rstTarget![Categories] = Nz(.Categories) rstTarget![Children] = Nz(.Children) rstTarget![PersonalHomePage] = _ Nz(.PersonalHomePage) rstTarget![Email1Address] = Nz(.Email1Address) rstTarget![Email1DisplayName] = _ Nz(.Email1DisplayName) rstTarget![Email2Address] = Nz(.Email2Address) rstTarget![Email2DisplayName] = _ Nz(.Email2DisplayName) rstTarget![Email3Address] = Nz(.Email3Address) rstTarget![Email3DisplayName] = _
364
11
Nz(.Email3DisplayName) rstTarget![GovernmentIDNumber] = _ Nz(.GovernmentIDNumber) rstTarget![Hobby] = Nz(.Hobby) rstTarget![ManagerName] = Nz(.ManagerName) rstTarget![OrganizationalIDNumber] = _ Nz(.OrganizationalIDNumber) rstTarget![Profession] = Nz(.Profession) rstTarget![Spouse] = Nz(.Spouse) rstTarget![WebPage] = Nz(.WebPage) rstTarget![IMAddress] = Nz(.IMAddress)
365
Part II
TIP
If you always synchronize your Access contacts to the same Outlook folder, you can comment out the SelectContactFolder code segment and insert a hard-coded folder path instead; if you want to use the default local Contacts folder, just remove the apostrophe on the line Set fldContacts = nms.GetDefaultFolder(olFolderContacts), and either comment out or delete the SelectContactFolder code segment.
The other procedure, CreateDenormalizedContactsTable, is considerably more complex, because it has to take data from five linked tables, creating one record per contact and updating its fields from different tables:
Public Function CreateDenormalizedContactsTable() Called from cmdForms_Click on fmnuMain On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim lngTargetID As Long strQueryContacts As String strQueryContactIDs As String strQueryCompanyIDs As String strQueryContactAddresses As String strTargetCustomerID As String
Set dbs = CurrentDb strQueryContacts = qryAccessContacts strQueryContactIDs = qryContactIDsPhones strQueryCompanyIDs = qryCompanyIDsPhones strQueryContactAddresses = qryContactAddresses
The rstTarget recordset is based on tblAccessContacts; this is the table to be filled with denormalized data. rstSource represents the first table of linked Access data, tblContactInfo. Information from this table is written to matching fields in the target table, with special handling for attachments (see the section on attachments for more information on this topic):
Set rstSource = dbs.OpenRecordset(strQueryContacts, _ dbOpenDynaset) Set rstTarget = dbs.OpenRecordset(strTable, _ dbOpenDynaset) Do While Not rstSource.EOF
Create one record in the target table per contact, and write company and contact data to it; also create one record in the match table per contact, for use in comparing contacts:
366
11
rstTarget.AddNew rstTarget![CustomerID] = Nz(rstSource!CustomerID) strTargetCustomerID = rstTarget![CustomerID] rstTarget![CompanyName] = _ Nz(rstSource!CompanyName) rstTarget![Account] = Nz(rstSource!Account) rstTarget![Categories] = Nz(rstSource!Categories) rstTarget![OrganizationalIDNumber] = _ Nz(rstSource!OrganizationalIDNumber) rstTarget![WebPage] = Nz(rstSource!WebPage) rstTarget![FTPSite] = Nz(rstSource!FTPSite) rstTarget![Title] = Nz(rstSource!Title) rstTarget![FirstName] = Nz(rstSource!FirstName) rstTarget![MiddleName] = Nz(rstSource!MiddleName) rstTarget![LastName] = Nz(rstSource!LastName) rstTarget![Suffix] = Nz(rstSource!Suffix) rstTarget![Nickname] = Nz(rstSource!Nickname) rstTarget![Department] = Nz(rstSource!Department) rstTarget![JobTitle] = Nz(rstSource!JobTitle) rstTarget![AssistantName] = Nz(rstSource!AssistantName) rstTarget![Birthday] = Nz(rstSource!Birthday) rstTarget![Anniversary] = Nz(rstSource!Anniversary) rstTarget![Children] = Nz(rstSource!Children) rstTarget![GovernmentIDNumber] = _ Nz(rstSource!GovernmentIDNumber) rstTarget![Hobby] = Nz(rstSource!Hobby) rstTarget![ManagerName] = Nz(rstSource!ManagerName) rstTarget![Profession] = Nz(rstSource!Profession) rstTarget![Spouse] = Nz(rstSource!Spouse)
367
Part II
The next source object is qryContactIDsPhones (see Figure 11.10). It has only two fields, so to match the many phone and ID fields in the target table I created a query with many calculated fields, one for each phone or ID field in tblAccessContacts.
FIGURE 11.10 A calculated field that converts a phone number in tblContactIDsPhones into a value to be written to tblAccessContacts.
Each calculated field returns a value for a phone number or ID matching one of the standard Outlook Phone and ID selections; a portion of the code that works with this query is listed below:
Set rstSource = dbs.OpenRecordset(strQueryContactIDs, _ dbOpenDynaset) Do While Not rstSource.EOF
Search for target record and update Contact ID and phone fields:
strTargetCustomerID = rstSource![CustomerID] strSearch = [CustomerID] = & Chr$(39) _ & strTargetCustomerID & Chr$(39)
Uncomment the following line to inspect the search string in the Immediate window.
Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = False Then GoTo NextSourceRecord1 End If rstTarget.Edit rstTarget![AssistantTelephoneNumber] = _ Nz(rstSource!AssistantTelephoneNumber)
368
11
rstTarget![BusinessFaxNumber] = _ Nz(rstSource!BusinessFaxNumber) rstTarget![BusinessTelephoneNumber] = _ Nz(rstSource!BusinessTelephoneNumber) rstTarget![Business2TelephoneNumber] = _ Nz(rstSource!Business2TelephoneNumber) rstTarget![CallbackTelephoneNumber] = _ Nz(rstSource!CallbackTelephoneNumber) rstTarget![CarTelephoneNumber] = _ Nz(rstSource!CarTelephoneNumber) rstTarget![HomeFaxNumber] = _ Nz(rstSource!HomeFaxNumber) rstTarget![HomeTelephoneNumber] = _ Nz(rstSource!HomeTelephoneNumber) rstTarget![Home2TelephoneNumber] = _ Nz(rstSource!Home2TelephoneNumber) rstTarget![ISDNNumber] = Nz(rstSource!ISDNNumber) rstTarget![MobileTelephoneNumber] = _ Nz(rstSource!MobileTelephoneNumber) rstTarget![OtherFaxNumber] = _ Nz(rstSource!OtherFaxNumber) rstTarget![OtherTelephoneNumber] = _ Nz(rstSource!OtherTelephoneNumber) rstTarget![PagerNumber] = Nz(rstSource!PagerNumber) rstTarget![PrimaryTelephoneNumber] = _ Nz(rstSource!PrimaryTelephoneNumber) rstTarget![RadioTelephoneNumber] = _ Nz(rstSource!RadioTelephoneNumber) rstTarget![TTYTDDTelephoneNumber] = _ Nz(rstSource!TTYTDDTelephoneNumber) rstTarget![TelexNumber] = Nz(rstSource!TelexNumber) rstTarget![Email1Address] = _ Nz(rstSource!Email1Address) rstTarget![Email1DisplayName] = _ Nz(rstSource!Email1DisplayName) rstTarget![Email2Address] = _ Nz(rstSource!Email2Address) rstTarget![Email2DisplayName] = _ Nz(rstSource!Email2DisplayName) rstTarget![Email3Address] = _ Nz(rstSource!Email3Address) rstTarget![Email3DisplayName] = _ Nz(rstSource!Email3DisplayName) rstTarget![IMAddress] = Nz(rstSource!IMAddress) rstTarget![PersonalHomePage] = _ Nz(rstSource!PersonalHomePage) rstTarget.Update
369
Part II
Company phones and IDs are handled similarly; only one possible value (Company Phone) is synchronized, because that is the only one that matches a field in Outlook:
Set rstSource = dbs.OpenRecordset(strQueryCompanyIDs, _ dbOpenDynaset) Do While Not rstSource.EOF
Finally, contact addresses are processed, using a query that converts each address field to the appropriate Business, Home, or Other address field in the target table. Figure 11.11 shows one of the calculated fields in this query.
370
11
The rstSource recordset is then selected, based on a query that selects contact addresses; the code looks for a matching target record, and if it is found, it is updated with information from the recordset:
Set rstSource = _ dbs.OpenRecordset(strQueryContactAddresses, _ dbOpenDynaset) Do While Not rstSource.EOF strTargetCustomerID = rstSource![CustomerID] strSearch = [CustomerID] = & Chr$(39) _ & strTargetCustomerID & Chr$(39) Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch rstTarget.Edit rstTarget![BusinessAddressStreet] = _ Nz(rstSource!BusinessAddressStreet) rstTarget![BusinessAddressPostOfficeBox] = _ Nz(rstSource!BusinessAddressPostOfficeBox) rstTarget![BusinessAddressCity] = _ Nz(rstSource!BusinessAddressCity) rstTarget![BusinessAddressState] = _ Nz(rstSource!BusinessAddressState) rstTarget![BusinessAddressPostalCode] = _ Nz(rstSource!BusinessAddressPostalCode) rstTarget![BusinessAddressCountry] = _ Nz(rstSource!BusinessAddressCountry)
371
Part II
rstTarget![HomeAddressStreet] = _ Nz(rstSource!HomeAddressStreet) rstTarget![HomeAddressPostOfficeBox] = _ Nz(rstSource!HomeAddressPostOfficeBox) rstTarget![HomeAddressCity] = _ Nz(rstSource!HomeAddressCity) rstTarget![HomeAddressState] = _ Nz(rstSource!HomeAddressState) rstTarget![HomeAddressPostalCode] = _ Nz(rstSource!HomeAddressPostalCode) rstTarget![HomeAddressCountry] = _ Nz(rstSource!HomeAddressCountry) rstTarget![OtherAddressStreet] = _ Nz(rstSource!OtherAddressStreet) rstTarget![OtherAddressPostOfficeBox] = _ Nz(rstSource!OtherAddressPostOfficeBox) rstTarget![OtherAddressCity] = _ Nz(rstSource!OtherAddressCity) rstTarget![OtherAddressState] = _ Nz(rstSource!OtherAddressState) rstTarget![OtherAddressPostalCode] = _ Nz(rstSource!OtherAddressPostalCode) rstTarget![OtherAddressCountry] = _ Nz(rstSource!OtherAddressCountry) rstTarget.Update NextSourceRecord3: rstSource.MoveNext Loop strTitle = Access table created strPrompt = Denormalized table of Access data ( _ & strTable & ) created MsgBox strPrompt, vbInformation + vbOKOnly, _ strTitle ErrorHandlerExit: rstSource.Close rstTarget.Close Exit Function ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Function
372
11
The two tables (tblOutlookContacts and tblAccessContacts) have matching fields; they are displayed in subforms on the two forms used for comparing Access and Outlook contact data. Figure 11.12 shows the form that compares contacts by Contact ID (frmCompareContactsByID), with data from an Access contact on the left and the matching Outlook contact (if there is one) on the right.
FIGURE 11.12 A form that compares Outlook and Access contacts by ContactID.
373
Part II
FIGURE 11.13 A form that compares Outlook and Access contacts by name.
374
11
TABLE 11.1
Go to next contact record Mark contact for deletion Copy all Access contacts to Outlook Copy all Outlook contacts to Access Modify Access contact to match Outlook contact Modify Outlook contact to match Access contact Go to next contact record Mark contact for deletion Copy all Access contacts to Outlook Copy all Outlook contacts to Access Create new Outlook contact to match Access contact Go to next contact record Mark contact for deletion Copy all Access contacts to Outlook Copy all Outlook contacts to Access Create new Access contact to match Outlook contact Go to next contact record Mark contact for deletion Copy all Access contacts to Outlook Copy all Outlook contacts to Access
No Outlook contact
No Access contact
375
Part II
To copy data in one field, rather than updating an entire contact record, select either Access to Outlook or Outlook to Access in the combo box in the center Copy Field Data section of the form, as shown in Figure 11.15, where the value Vice President in the Access contact record is being replaced by Senior Vice President from the Outlook record. You can also type in new data, or edit existing data, as needed, before copying the record.
If you want to completely remove a contact, select Mark Record for Deletion and it will be deleted when the contacts are updated. When you have finished copying, editing, and marking records for deletion, the Update Contact Information button on the main menu offers you a choice of updating the Access contacts first, and then the Outlook contacts. All data (including attachments, if any) from tblOutlookContacts will be copied back to the contacts in the selected Contacts folder, creating new contacts as needed. The procedure that updates the Outlook contacts is listed here:
Public Sub UpdateAllOutlookContacts() Called from cmdUpdateContactInfo_Click() on fmnuMain On Error GoTo ErrorHandler Set appOutlook = GetObject(, Outlook.Application) Set nms = appOutlook.GetNamespace(MAPI) strTable = tblOutlookContacts Set dbs = CurrentDb Set rstSource = _ dbs.OpenRecordset(strTable, dbOpenDynaset)
376
11
You can use the following lines to export to the default local Contacts folder, or a hard-coded folder of your choice. To use the default Contacts folder, just remove the apostrophe at the beginning of the next line (this is called uncommenting a line of code, because the apostrophe in front of the line turns it into a comment); to use a hard-coded custom folder, enter its name.
Set fldContacts = nms.GetDefaultFolder(olFolderContacts) GoTo UpdateContacts
Use the following section of code to allow selection of a custom Contacts folder from the Folder Picker dialog:
SelectContactFolder: On Error Resume Next Set fldContacts = nms.PickFolder If fldContacts Is Nothing Then strTitle = Select Folder strPrompt = Please select a Contacts folder MsgBox strPrompt, vbExclamation + vbOKOnly, strTitle GoTo SelectContactFolder End If Debug.Print Default item type: _ & fldContacts.DefaultItemType If fldContacts.DefaultItemType <> olContactItem Then MsgBox strPrompt, vbExclamation + vbOKOnly, _ strTitle GoTo SelectContactFolder End If UpdateContacts: Do While Not rstSource.EOF
Search for each contact in selected Contacts folder in case it already exists, and set a reference to it, searching first by CustomerID and then by first name and last name (Outlook contacts may lack a value in the CustomerID property):
strCustomerID = Nz(rstSource![CustomerID]) strSearch = [CustomerID] = & Chr$(39) _ & strCustomerID & Chr$(39) Debug.Print Search string: & strSearch blnDelete = rstSource![Delete]
Search by CustomerID.
Set con = fldContacts.Items.Find(strSearch) If TypeName(con) = Nothing Then Debug.Print Customer ID & strCustomerID _ & not found in & fldContacts.Name & folder strFirstName = Nz(rstSource![FirstName]) strLastName = Nz(rstSource![LastName]) strSearch = [FirstName] = & Chr$(39) _
377
Part II
& strFirstName & Chr$(39) _ & And [LastName] = & Chr$(39) _ & strLastName & Chr$(39) Debug.Print Search string: & strSearch
Search by name.
Set con = fldContacts.Items.Find(strSearch) If TypeName(con) = Nothing Then Debug.Print Contact name & strFirstName _ & & strLastName & not found in _ & fldContacts.Name & folder
Update contact item with values from controls on the Outlook subform:
On Error GoTo ErrorHandler con.CustomerID = Nz(rstSource![CustomerID]) con.Title = Nz(rstSource![Title]) con.FirstName = Nz(rstSource![FirstName]) con.MiddleName = Nz(rstSource![MiddleName]) con.LastName = Nz(rstSource![LastName]) con.Suffix = Nz(rstSource![Suffix]) con.Nickname = Nz(rstSource![Nickname])
378
11
con.CompanyName = Nz(rstSource![CompanyName]) con.Department = Nz(rstSource![Department]) con.JobTitle = Nz(rstSource![JobTitle]) con.BusinessAddressStreet = _ Nz(rstSource![BusinessAddressStreet]) con.BusinessAddressPostOfficeBox = _ Nz(rstSource![BusinessAddressPostOfficeBox]) con.BusinessAddressCity = _ Nz(rstSource![BusinessAddressCity]) con.BusinessAddressState = _ Nz(rstSource![BusinessAddressState]) con.BusinessAddressPostalCode = _ Nz(rstSource![BusinessAddressPostalCode]) con.BusinessAddressCountry = _ Nz(rstSource![BusinessAddressCountry]) con.BusinessHomePage = _ Nz(rstSource![BusinessHomePage]) con.FTPSite = Nz(rstSource![FTPSite]) con.HomeAddressStreet = _ Nz(rstSource![HomeAddressStreet]) con.HomeAddressPostOfficeBox = _ Nz(rstSource![HomeAddressPostOfficeBox]) con.HomeAddressCity = _ Nz(rstSource![HomeAddressCity]) con.HomeAddressState = _ Nz(rstSource![HomeAddressState]) con.HomeAddressPostalCode = _ Nz(rstSource![HomeAddressPostalCode]) con.HomeAddressCountry = _ Nz(rstSource![HomeAddressCountry]) con.OtherAddressStreet = _ Nz(rstSource![OtherAddressStreet]) con.OtherAddressPostOfficeBox = _ Nz(rstSource![OtherAddressPostOfficeBox]) con.OtherAddressCity = _ Nz(rstSource![OtherAddressCity]) con.OtherAddressState = _ Nz(rstSource![OtherAddressState]) con.OtherAddressPostalCode = _ Nz(rstSource![OtherAddressPostalCode]) con.OtherAddressCountry = _ Nz(rstSource![OtherAddressCountry]) con.AssistantTelephoneNumber = _ Nz(rstSource![AssistantTelephoneNumber]) con.BusinessFaxNumber = _ Nz(rstSource![BusinessFaxNumber]) con.BusinessTelephoneNumber = _ Nz(rstSource![BusinessTelephoneNumber]) con.Business2TelephoneNumber = _ Nz(rstSource![Business2TelephoneNumber])
379
Part II
con.CallbackTelephoneNumber = _ Nz(rstSource![CallbackTelephoneNumber]) con.CarTelephoneNumber = _ Nz(rstSource![CarTelephoneNumber]) con.CompanyMainTelephoneNumber = _ Nz(rstSource![CompanyMainTelephoneNumber]) con.HomeFaxNumber = Nz(rstSource![HomeFaxNumber]) con.HomeTelephoneNumber = _ Nz(rstSource![HomeTelephoneNumber]) con.Home2TelephoneNumber = _ Nz(rstSource![Home2TelephoneNumber]) con.ISDNNumber = Nz(rstSource![ISDNNumber]) con.MobileTelephoneNumber = _ Nz(rstSource![MobileTelephoneNumber]) con.OtherFaxNumber = Nz(rstSource![OtherFaxNumber]) con.OtherTelephoneNumber = _ Nz(rstSource![OtherTelephoneNumber]) con.PagerNumber = Nz(rstSource![PagerNumber]) con.PrimaryTelephoneNumber = _ Nz(rstSource![PrimaryTelephoneNumber]) con.RadioTelephoneNumber = _ Nz(rstSource![RadioTelephoneNumber]) con.TTYTDDTelephoneNumber = _ Nz(rstSource![TTYTDDTelephoneNumber]) con.TelexNumber = Nz(rstSource![TelexNumber]) con.Account = Nz(rstSource![Account]) con.AssistantName = Nz(rstSource![AssistantName]) con.Categories = Nz(rstSource![Categories]) con.Children = Nz(rstSource![Children]) con.PersonalHomePage = _ Nz(rstSource![PersonalHomePage]) con.Email1Address = Nz(rstSource![Email1Address]) con.Email1DisplayName = _ Nz(rstSource![Email1DisplayName]) con.Email2Address = Nz(rstSource![Email2Address]) con.Email2DisplayName = _ Nz(rstSource![Email2DisplayName]) con.Email3Address = Nz(rstSource![Email3Address]) con.Email3DisplayName = _ Nz(rstSource![Email3DisplayName]) con.GovernmentIDNumber = _ Nz(rstSource![GovernmentIDNumber]) con.Hobby = Nz(rstSource![Hobby]) con.ManagerName = Nz(rstSource![ManagerName]) con.OrganizationalIDNumber = _ Nz(rstSource![OrganizationalIDNumber]) con.Profession = Nz(rstSource![Profession]) con.Spouse = Nz(rstSource![Spouse]) con.WebPage = Nz(rstSource![WebPage]) con.IMAddress = Nz(rstSource![IMAddress])
380
11
Use special date handling (a blank date in Outlook is actually a date of 1/1/4501):
If Nz(rstSource![Birthday]) <> Then con.Birthday = Nz(rstSource![Birthday]) Else con.Birthday = #1/1/4501# End If If Nz(rstSource![Anniversary]) <> Then con.Anniversary = Nz(rstSource![Anniversary]) Else con.Anniversary = #1/1/4501# End If
381
Part II
End Sub
The UpdateAllAccessContacts procedure has the more complex task of copying updated contact data from tblAccessContacts back to the linked contact tables. This procedure does the reverse of the CreateDenormalizedContactsTable procedure; using tblAccessContacts as a data source, it updates the linked contact data in tblCompanyInfo, tblContactInfo, tblCompanyIDsPhones, tblContactAddresses, and tblContactIDsAndPhones, creating new records as needed:
Public Sub UpdateAllAccessContacts() Called from cmdUpdateContactInfo_Click() on fmnuMain On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim lngContactID As Long lngCompanyID As Long strSourceTable As String strTarget As String strAddressType As String strDescription As String
Set dbs = CurrentDb strSourceTable = tblAccessContacts Set rstSource = dbs.OpenRecordset(strSourceTable, _ dbOpenDynaset) UpdateCompanyInfo: Do While Not rstSource.EOF Debug.Print Processing Target ID: _ & rstSource![TargetID]
Search for matching Company record in target table, and update it if found; otherwise, create new company record, and write company data to it.
strTarget = tblCompanyInfo Set rstTarget = dbs.OpenRecordset(strTarget, _ dbOpenDynaset) blnDelete = rstSource![Delete] If blnDelete = True Then
To avoid problems with deleting records in a table on the one side of a one-to-many relationship, before updating the tables, the procedure runs three delete queries to delete records linked to contacts marked for deletion:
382
11
On Error Resume Next DoCmd.SetWarnings False DoCmd.OpenQuery qdelContactIDs DoCmd.OpenQuery qdelContactAddresses DoCmd.OpenQuery qdelContacts GoTo NextSourceRecord End If
Search for matching contact record in target table, and update it if found; otherwise, create a new contact record, and write contact data to it:
strTarget = tblContactInfo Set rstTarget = dbs.OpenRecordset(strTarget, _ dbOpenDynaset) strCustomerID = rstSource![CustomerID] lngContactID = CLng(strCustomerID) strSearch = [ContactID] = & lngContactID Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = True Then
383
Part II
384
11
To update data in tblContactAddresses, if there is data in any of the Business address fields, the strAddressType variable is set to Business, and the code searches for matching records in tblContactAddresses. If none are found, a new address record is created; if a record is found, its fields are updated from the appropriate fields in tblAccessContacts. The Home and Other address fields are handled similarly:
strTarget = tblContactAddresses Set rstTarget = dbs.OpenRecordset(strTarget, _ dbOpenDynaset)
385
Part II
_ Then _
386
11
strSearch = [ContactID] = & lngContactID _ & And [AddressType] = & Chr$(39) _ & strAddressType & Chr$(39) Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = True Then
If there is a value in the Company Phone record in the source database, it is written to a record in the target table (this is the only company phone number or ID that can be matched with Outlook, so it is the only one that is synchronized). If none is found, a new record is created and updated with the Company Phone number:
strTarget = tblCompanyIDsPhones Set rstTarget = dbs.OpenRecordset(strTarget, _ dbOpenDynaset) strDescription = Company Phone strSearch = [CompanyID] = & lngCompanyID _ & And [Description] = & Chr$(39) _ & strDescription & Chr$(39) Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = True Then
387
Part II
The Contact IDs and Phones in tblContactIDsPhones are updated in a similar manner: First the code searches for a value in one of these fields, and if it is found, the strDescription variable is set with the phone or ID description, and a record is sought using ContactID and strDescription. If a record is found, it is updated; otherwise a new record is created in tblContactIDsAndPhones and the phone number or ID is written to it:
UpdateContactIDs:
Search for a matching Contact ID record in the target table, and update it if found; otherwise, create a new record, and write Contact ID data to it.
strTarget = tblContactIDsPhones Set rstTarget = dbs.OpenRecordset(strTarget, _ dbOpenDynaset) If Nz(rstSource![AssistantTelephoneNumber]) <> Then strDescription = Assistant Phone strSearch = [ContactID] = & lngContactID _ & And [Description] = & Chr$(39) & _ strDescription & Chr$(39) Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = True Then
388
11
If Nz(rstSource![BusinessFaxNumber]) <> Then strDescription = Business Fax strSearch = [ContactID] = & lngContactID _ & And [Description] = & Chr$(39) _ & strDescription & Chr$(39) Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = True Then
[I am not listing a great number of similar code segments, each of which updates a different phone number or ID.]
If Nz(rstSource![PersonalHomePage]) <> Then strDescription = Web Page strSearch = [ContactID] = & lngContactID _ & And [Description] = & Chr$(39) _ & strDescription & Chr$(39) Debug.Print Search string: & strSearch rstTarget.FindFirst strSearch If rstTarget.NoMatch = True Then
389
Part II
Loop rstTarget.Close strTitle = Access tables updated strPrompt = Linked Access tables of contact data _ & updated from form MsgBox strPrompt, vbInformation + vbOKOnly, strTitle ErrorHandlerExit: On Error Resume Next rstSource.Close rstTarget.Close Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
In case you want to copy all the Access contacts to Outlook, or vice versa, there are two selections on the Select Action combo boxs list that will let you do this. The Copy All Access Contacts to Outlook selection runs the CopyAccessContactsToOutlook procedure, which first puts up a confirmation message to ensure that the user wants to wipe out the existing Outlook contacts, and replace them with contacts copied from Access. If the user clicks Yes, the procedure first calls the CreateDenormalizedContactsTable procedure to write data to tblAccessContacts, and then runs code that is similar to the code in the UpdateAllOutlookContacts procedure, except that it skips the searching and just creates all new Outlook contact items. Similarly, the Copy All Outlook Contacts to Access selection runs the CopyAllOutlook ContactsToAccess procedure, which asks for confirmation, then runs the ImportOutlook Contacts procedure to write data from Outlook contacts to tblOutlookContacts, then runs code that is similar to the code in the UpdateAllAccessContacts procedure, except that it doesnt search for matching records, just creates new Access records for all the Outlook contact records.
NOTE
When copying all Outlook contacts to Access, you will end up with Access and Outlook contacts whose CustomerID values dont match. This is because the ContactID field in tblContactInfo is an AutoNumber field, so it cant be set to a specific value. There are two ways to deal with this discrepancy: Use the Compare by Name form if you dont care whether the Customer ID is the same in Access and Outlook; or select the Copy All Access Contacts to Outlook selection to save the Access ContactID AutoNumber value back to the matching Outlook records.
390
11
NEW FEATURE
When you add a field of the Attachment data type to an Access 2007 table, it has three subfields, which you can see in the Relationships diagram (see Figure 11.1). The attachment itself is stored in the FileData subfield; its file name and path in the FileName subfield, and the file type in the FileType subfield. Generally, you only need to work with the FileData and FileName subfields when copying Access attachments. The situation with Outlook attachments is simpler: you just save the attachment file name and path to the Attachments collection of an item, using the Add method of that collection. Most likely, you will have some attachments, either in your Outlook contacts, or in Access contact records, so my synchronizing code needs to handle attachments. To copy attachments from one place to another, you need to save them to files in a folder; the folder used for this purpose is selected using the Attachments Folder Path button on the main menu, which runs an event procedure that pops up a Folder Picker dialog. The procedures listed next are called from the longer procedures that do the copying of data between the two Access compare tables, as seen on the two contact comparison forms, or between Access and Outlook:
Public Sub CopyAccessAttsToOutlook(con As _ Outlook.ContactItem, rstSourceAttachments As _ DAO.Recordset2) Called from UpdateAllOutlookContacts On Error GoTo ErrorHandler Set fso = CreateObject(Scripting.FileSystemObject) With rstSourceAttachments Do While Not .EOF strDocsPath = GetOutputDocsPath
Need to extract the file name from the FileName field, using the SplitFileName function, because it sometimes contains the path (sometimes multiple times) as well as the file name.
391
Part II
strFile = _ SplitFileName(rstSourceAttachments.Fields(FileName)) Debug.Print File name: & strFile strFileAndPath = strDocsPath & strFile Debug.Print File and path: & strFileAndPath On Error Resume Next
Check whether this file already exists in the folder, and save it to the folder if not.
Set fil = fso.GetFile(strFileAndPath) If fil Is Nothing Then
Add this attachment to the Attachments collection of the Outlook contact item.
Debug.Print Adding attachment & strFileAndPath _ & to & con.FullName & contact con.Save con.Attachments.Add Source:=strFileAndPath, _ Type:=olByValue con.Close (olSave) Kill strFileAndPath .MoveNext Loop rstSourceAttachments.Close End With ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub Public Sub CopyOutlookAttsToAccess(con _ As Outlook.ContactItem, rstTargetAttachments As _ DAO.Recordset2)
392
11
Called from NewAccessContactAndID and ImportOutlookContacts On Error GoTo ErrorHandler Set fso = CreateObject(Scripting.FileSystemObject) For Each att In con.Attachments Extract the file name from the Attachment FileName property. strFile = att.FileName strDocsPath = GetOutputDocsPath strFileAndPath = strDocsPath & strFile Debug.Print File and path: & strFileAndPath On Error Resume Next
Check whether this file already exists in the folder, and save it to the folder if not
Set fil = fso.GetFile(strFileAndPath) If fil Is Nothing Then
393
Part II
Public Sub CopyAccessAttsToAccess(rstSourceAttachments _ As DAO.Recordset2, rstTargetAttachments _ As DAO.Recordset2) Called from CreateDenormalizedContactsTable, UpdateAllAccessContacts, UpdateOutlookContactID, UpdateAccessContactID, UpdateOutlookContactName, UpdateAccessContactName, UpdateAllAccessContacts, UpdateOutlookContactID, cboAttachments_Click on fsubCopyFieldData On Error GoTo ErrorHandler Set fso = CreateObject(Scripting.FileSystemObject) Do While Not rstSourceAttachments.EOF
Need to extract the file name from the FileName field, using the SplitFileName function, because it sometimes contains the path (sometimes multiple times) as well as the file name.
strFile = _ SplitFileName(rstSourceAttachments.Fields(FileName)) Debug.Print File name: & strFile strFileAndPath = strDocsPath & strFile Debug.Print File and path: & strFileAndPath On Error Resume Next
Check whether this file already exists in the folder, and save it to the folder if not.
Set fil = fso.GetFile(strFileAndPath) If fil Is Nothing Then
394
11
Loop rstSourceAttachments.Close rstTargetAttachments.Close ErrorHandlerExit: Exit Sub ErrorHandler: If Err.Number = 3839 Then File already exists; delete it Kill strFileAndPath Resume Else MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End If End Sub Function SplitFileName(strFileAndPath) As String On Error GoTo ErrorHandler Dim strFullPath() As String Dim intUBound As Integer
Extract the file name from the variable with the file and path.
strFullPath = Split(strFileAndPath, \, -1, vbTextCompare) intUBound = UBound(strFullPath) strFile = strFullPath(intUBound) SplitFileName = strFile ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & Err.Number & ; Description: & Err.Description Resume ErrorHandlerExit End Function
Figure 11.16 shows an Outlook contact with an attachment created from an Access contact record.
395
Part II
FIGURE 11.16 An Outlook contact with an attachment created from an Access contact record.
Summary
The synchronizing techniques in this chapter will allow you to maintain your contact data in a set of linked Access tables, adding any number of custom phone numbers and IDs as needed, and synchronize your Access data to Outlook contacts, so you can work with the standard properties of contacts in the convenient Outlook contact interface, and work with any additional custom fields you need in Access, without having to manually enter (and update) the same contact data in Access and Outlook. In addition, you can use the denormalizing and renormalizing techniques used to work with contacts for any situation that requires converting a set of linked Access tables to a single flat-file table, or the reverse something that is likely to arise when exchanging data with legacy programs or mainframe databases.
396
arlier chapters in this book described how to work with Word, Excel, and Outlook, using VBA code to create Word documents, Excel worksheets, and Outlook items and fill them with data from Access. This chapter describes some more advanced techniques for working with other Office components, such as those you might need in a database with shipping and ordering information.
IN THIS CHAPTER
Creating Word shipping labels with information about shipments Creating Excel PivotCharts filled with Access data Emailing Access reports of shipping and reordering information
NOTE
You might also need to print more information on your shipping labels in addition to the address, such as the Order No., Product No., Product Name, Category, the case number in a sequence of cases, or other such data. To print labels with extra information, or to make decisions on whether a set of labels should be printed, you need more elaborate VBA code.
397
Part II
The sample Northwind Plus database contains tables from the Northwind.mdb database, with several extra fields in some tables, queries (used in the next section), and three new forms two main forms (one for selecting records for shipping and one for reordering depleted inventory) plus a supplementary form for editing product amounts.
NOTE
When opening Northwind Plus in Vista, you may get the security alert shown in Figure 12.1. To temporarily enable the databases code, click the Enable Content button and select the Enable this content option in the dialog. To prevent this security alert from appearing every time you open the database, sign the VBA code with a digital signature, as described in the sidebar.
FIGURE 12.1 A Vista security alert when opening a database with unsigned VBA code.
398
12
399
Part II
400
12
10. If this is the first time you have used this certificate, the Microsoft Office Access Security Notice dialog opens:
continued
401
Part II
continued 11. You can click the Show Signature Details to view the available information about the selected certificate:
12. After clicking the Trust all from publisher button on the Microsoft Office Access Security Notice dialog, you can work on the database. 13. In future, if you open the package file, you will get a dialog offering to extract the database:
14. On clicking OK, the extracted database opens as a normal Access 2007 database.
402
12
Figure 12.2 shows the main menu of the Northwind Plus sample database, with buttons for opening the two main forms, and buttons to select the Documents and Templates folders. The folder selection buttons function like the similar controls in several other chapters, using a Folder Picker dialog created by the Office FileDialog object.
FIGURE 12.2 The main menu of the Northwind Plus sample database.
NOTE
I could have determined which orders were ready to ship just by examining the available inventory and the date the order is required, but in the real world these are not the only factors to consider if there arent enough people to do the shipping, or all the trucks are out on the road, you cant ship the product, even if there is enough inventory.
403
Part II
FIGURE 12.3 The form used for inspecting orders and marking them for shipment.
The Select Orders for Shipping form (frmSelectOrdersForShipping) is a main form with a datasheet subform. It is used to inspect orders that are in the correct time range for shipping and mark them for shipping now. The main form displays fields from the orders, and the datasheet subform lists the products on the selected order. On the main form, the Ship Partial checkbox is checked if a partial order can be shipped, in case there is enough inventory to ship at least one product on the order. The Ready to Ship checkbox indicates that the order is ready to ship, taking into account factors over and above the available inventory. To print shipping labels with extra information, I made a query (qryNorthwindShippingLabels), based on qryNorthwindAll (a query that includes all the linked Northwind data tables), containing all the information to print on the labels. Apart from the shipping name and address, the query also includes the following fields: n OrderDate n RequiredDate (with a criterion of >Date()) And <DateAdd(d,30,Date()) to just include orders required from tomorrow to less than 30 days in the future n DateShipped n Supplier (an alias for CompanyName in tblSuppliers) n ProductID n ProductName n OrderID n NoCases (an alias for Quantity)
404
12
n CategoryName n ReadyToShip, a Boolean field with a criterion of True to select orders that are ready to ship The cmdCreateLabels event procedure is listed here, with explanation of how it processes the sets of labels to print:
Private Sub cmdCreateLabels_Click() On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim appWord As Word.Application blnShipPartial As Boolean dbs As DAO.Database doc As Word.Document fil As Scripting.File fso As New Scripting.FileSystemObject lngCaseNo As Long lngNoCases As Long lngCasesInStock As Long lngCount As Long lngSet As Long lngNoSets As Long lngOrderID As Long lngSetNo As Long lngSubtract As Long rstOrder As DAO.Recordset rstShip As DAO.Recordset strCategory As String strDocsPath As String strOrderDate As String lngProductID As Long strProductName As String strPrompt As String strQueryShip As String strQueryOrder As String strSaveName As String strSaveNameAndPath As String strShipAddress As String strShipCityStatePC As String strShipCountry As String strShipDate As String strShipName As String strSQL As String strSupplier As String strTemplate As String strTemplateNameAndPath As String strTemplatePath As String strTitle As String varValue As Variant
405
Part II
Get User Templates and Documents paths from the users selections on the main menu, using two functions that pick up the saved paths from tblInfo:
strTemplatePath = GetTemplatesPath Debug.Print Template path: & strTemplatePath strDocsPath = GetDocumentsPath Debug.Print Documents folder: & strDocsPath strTemplate = Avery 5164 Shipping Labels.dotx strTemplateNameAndPath = strTemplatePath & strTemplate Debug.Print Template name and path: _ & strTemplateNameAndPath On Error Resume Next
Look for the template in the templates folder, by attempting to set a FileSystemObject File variable to it:
Set fil = fso.GetFile(strTemplateNameAndPath) If fil Is Nothing Then strPrompt = Cant find & strTemplate & in _ & strTemplatePath & ; canceling MsgBox strPrompt, vbCritical + vbOKOnly GoTo ErrorHandlerExit End If On Error GoTo ErrorHandler
406
12
Set up a recordset (rstShip) based on the qrySelectedNorthwindShippingLabels query, which has one record for each product on a selected order:
Set dbs = CurrentDb strQueryShip = qrySelectedNorthwindShippingLabels Set rstShip = dbs.OpenRecordset(strQueryShip)
Get the number of records for use in updating the progress meter:
rstShip.MoveLast rstShip.MoveFirst lngNoSets = rstShip.RecordCount
Start the progress meter in the status bar, using the SysCmd object:
strPrompt = Creating & lngNoSets _ & sets of shipping labels Application.SysCmd acSysCmdInitMeter, strPrompt, _ lngNoSets
Set up a loop for processing the sets of labels for the orders:
For lngSet = 1 To lngNoSets lngOrderID = rstShip![OrderID] blnShipPartial = rstShip![ShipPartial]
Create a filtered recordset (rstOrder) for this order only, with records corresponding to the products on the order:
strQueryOrder = qryOrder Set dbs = CurrentDb strSQL = SELECT * FROM & strQueryShip & WHERE _ & [OrderID] = & lngOrderID & ; Debug.Print SQL for & strQueryOrder & : & strSQL lngCount = CreateAndTestQuery(strQueryOrder, strSQL) Debug.Print No. of records found: & lngCount Set rstOrder = dbs.OpenRecordset(strQueryOrder)
Set up a loop to process each product on this order, checking whether there is enough inventory to ship the product on this order:
Do While Not rstOrder.EOF lngProductID = rstOrder![ProductID] strProductName = rstOrder![ProductName] lngNoCases = rstOrder![NoCases] lngCasesInStock = rstOrder![CasesInStock] If lngNoCases > lngCasesInStock Then If blnShipPartial = False Then
407
Part II
For orders with ShipPartial unchecked, cant ship the order because one product has inadequate inventory:
strTitle = Inadequate inventory strPrompt = Only & lngCasesInStock _ & cases in inventory; cant fill Order ID _ & lngOrderID & for & strProductName MsgBox strPrompt, vbExclamation, strTitle GoTo NextOrder ElseIf blnShipPartial = True Then
For orders with ShipPartial checked, cant ship this product on the order:
strTitle = Inadequate inventory strPrompt = Only & lngCasesInStock _ & cases in inventory; cant fill _ & strProductName & item on _ & Order ID & lngOrderID MsgBox strPrompt, vbExclamation, strTitle
Figure 12.4 shows a typical Inadequate inventory message when there isnt enough inventory to fill a product line item on an order with ShipPartial checked.
FIGURE 12.4 A message indicating that there is inadequate inventory to ship a product item on an order.
There is enough inventory to ship this product; create a new labels document for this set of labels from the template:
Set doc = _ appWord.Documents.Add(Template:= _ strTemplateNameAndPath, _ documenttype:=wdNewBlankDocument, _ Visible:=True) doc.Activate
408
12
Set variables with information to print on all the labels for this order:
strCategory = rstOrder![CategoryName] strOrderDate = CStr(rstOrder![OrderDate]) strShipName = rstOrder![ShipName] strShipAddress = rstOrder![ShipAddress] strShipCityStatePC = rstOrder![ShipCityStatePC] strShipCountry = rstOrder![ShipCountry] strSupplier = rstOrder![Supplier] strShipDate = Format(Date, dd-mmm-yyyy)
Set up a loop to print a set of labels for this order, one label per case shipped:
For lngCaseNo = 1 To lngNoCases With appWord.Selection
Put data into one label (one cell in the Word document):
.TypeText Text:=FROM: & vbTab .MoveLeft Unit:=wdCharacter, Count:=1 .MoveLeft Unit:=wdWord, Count:=2, _ Extend:=wdExtend .Font.Bold = True .EndKey Unit:=wdLine .Font.Bold = False .TypeText Text:=Northwind Traders .TypeParagraph
Indent the left margin to match the tab setting, so the address will line up with the name.
NOTE
Instead of looking up Word methods, properties, and other object model components in the Object Browser, you can capture the syntax for a Word action by recording a macro in Word, then copying and pasting the VBA code into your Access VBA procedure. Just insert your Word application variable where needed and trim the arguments you dont need to prepare the code for use in Access.
.ParagraphFormat.TabIndent (1) .TypeText Text:=2839 El Presidio St. .TypeParagraph .TypeText Text:=Nowhere, WA 92838 .TypeParagraph
409
Part II
Indent the left margin to match the tab setting, so the address will line up with the name:
.ParagraphFormat.TabIndent (1) .TypeText strShipAddress .TypeParagraph .TypeText strShipCityStatePC .TypeParagraph .TypeText strShipCountry .TypeParagraph
Return to the normal left margin before printing the extra information:
.ParagraphFormat.LeftIndent = 8 .TypeParagraph .Font.Size = 10 .Font.Bold = True .TypeText Order ID: & vbTab _ & CStr(lngOrderID) .TypeParagraph .TypeText Category: & vbTab _ & strCategory .TypeParagraph .TypeText Product: & vbTab & ID _ & lngProductID & ( _ & strProductName & ) .TypeParagraph .TypeText Supplier: & vbTab _ & strSupplier .TypeParagraph .TypeText Ship date: & vbTab _ & strShipDate .TypeParagraph .Font.Size = 12 .Font.Bold = False .TypeParagraph .TypeText vbTab & Case _ & lngCaseNo & of & lngNoCases .MoveRight Unit:=wdCell End With Next lngCaseNo
410
12
strSaveNameAndPath = strDocsPath & strSaveName Debug.Print Save name: & strSaveName On Error Resume Next
Check for the existence of a file with this name, and delete it if found:
Set fil = fso.GetFile(strSaveNameAndPath) If Not fil Is Nothing Then Kill strSaveNameAndPath End If On Error GoTo ErrorHandler doc.SaveAs FileName:=strSaveNameAndPath
Subtract the amount of product shipped from the in stock amount in tblProducts:
lngSubtract = lngCasesInStock - lngNoCases strSQL = UPDATE tblProducts SET _ & tblProducts.UnitsInStock = _ & lngSubtract & WHERE ProductID = _ & lngProductID Debug.Print SQL string: & strSQL DoCmd.RunSQL strSQL
411
Part II
strTitle = Set of labels created strPrompt = _ A set of shipping labels created _ & for Order ID & lngOrderID _ & , Product ID & lngProductID _ & ( & strProductName & ) MsgBox strPrompt, vbInformation, strTitle
Figure 12.5 shows the success message for the last set of labels, with the progress meter at full in the Access window status bar.
FIGURE 12.5 A message indicating that a set of labels has been created for an order.
412
12
& order shipped on _ & Format(Date, dd-mmm-yyyy) MsgBox strPrompt, vbInformation, strTitle ErrorHandlerExit:
Figure 12.6 shows the Finished! message after all the sets of labels have been created.
FIGURE 12.6 The success message after all labels have been printed.
The GetDocumentsPath function that retrieves the Documents path from tblInfo is listed next; the GetTemplatesPath function is similar:
Public Function GetDocumentsPath() As String On Error GoTo ErrorHandler Set dbs = CurrentDb Set rst = dbs.OpenRecordset(tblInfo)
413
Part II
rst.MoveFirst GetDocumentsPath = rst![DocumentsPath] & \ rst.Close ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & Err.Number & ; Description: & _ Err.Description Resume ErrorHandlerExit End Function
FIGURE 12.7 A set of Word shipping labels filled with data from Access.
414
12
Every development project has unique requirements; when using Access to create mailing labels (or any other type of Word documents), you have the freedom to create tables and forms that exactly meet your needs, storing all (and only) the information you need to select and print the data you need on Word documents. The code used to create the shipping labels described in this section includes several techniques that are useful for producing Word documents of various types, such as creating filtered recordsets for working with detail records, calculating x of y numbers when working with sets of records, displaying a progress meter in the status bar, and updating tables from code.
NOTE
When you create an Excel PivotChart, it is automatically created with a linked PivotTable.
The first step in creating an Excel PivotChart is to create an Access query with the data to be charted. Excel PivotCharts are not exactly the same as Access PivotCharts; in particular, they lack the date grouping feature that automatically creates a variety of date sorts from a Date field (Year, Month, Quarter, Week). If you want to analyze data in an Excel PivotChart by month, quarter, or year, you need to do the breakdown in an Access query, before exporting the data to Excel, or create the date groups manually in Excel by using the Group command. In Office 2007, you can create an Excel PivotChart manually, following these steps:
1. Create an Access query with the data to be charted; qryQuarterlySalesByCategory has only three fields: OrderQuarter, Category, and Price. The OrderQuarter field extracts the year and quarter from the OrderDate field, using this expression:
OrderQuarter: Year([OrderDate]) & Q & DatePart(q,[OrderDate])
2. Export this query to Excel using the Excel command in the Export group of the External Data tab of the Ribbon, as shown in Figure 12.8.
415
Part II
3. On the Export dialog, edit the worksheet name as desired, and browse for an alternate location if you dont want to store the worksheet in the default Documents folder (in Figure 12.9, I edited the worksheet name and left the folder at the default setting).
FIGURE 12.9 Editing the worksheet save name in the Export dialog.
416
12
4. Open the newly created worksheet in Excel. 5. Click anywhere in the data range, select the Insert tab of the Ribbon, and select PivotChart from the drop-down in the Tables group, as shown in Figure 12.10.
6. The Create PivotTable with PivotChart dialog opens, as shown in Figure 12.11, with the range preselected; just click OK to create the PivotChart in another worksheet in the same workbook.
417
Part II
418
12
8. The layout of a PivotChart is different in Excel than in Access (a discrepancy Microsoft should clear up, but that is another matter). The fields from the query are listed in the PivotTable Field List in the panel on the right of the worksheet; I dragged OrderQuarter to the Axis Fields drop zone, CategoryName to the Legend Fields drop zone, and Price to the Values drop zone (Excel automatically makes it a Sum of Price). Figure 12.13 shows the plainly formatted PivotChart at this point.
9. To format the left axis number, right-click any category value in the PivotChart and select Format Axis from the context menu, as shown in Figure 12.14.
419
Part II
10. The Format Axis dialog opens; I formatted the left axis number for U.S. Currency by selecting Number for the Axis Option, Currency for the Category, 0 decimal places, and $ English (U.S.) for the Symbol, as shown in Figure 12.15.
420
12
NOTE
When the PivotChart (or one of its components) is selected, you should see special PivotChart commands in the Design and Layout groups of the Ribbon; if you dont see them, click the PivotChart to give it the focus.
11. To give the chart a title (generally a good idea), I selected Layout 1 in the Chart Layouts group on the Design tab of the Ribbon (in PivotChart Tools mode), as shown in Figure 12.16.
12. This selection adds a Chart Title control to the chart; right-click it and select Edit Text to edit the chart name as desired; I made it Quarterly Sales by Category. 13. The final step is to select a chart style. The default style (contrasting color bars) is generally fine, but there are lots more choices available. To select a different chart style, drop down the More button at the lower-right corner of the Chart Styles group, as shown in Figure 12.17.
14. A palette of 54 styles opens as shown in Figure 12.18. 15. The formatted PivotChart is shown in Figure 12.19.
421
Part II
FIGURE 12.19 A PivotChart with contrasting color bars and a light background.
422
12
16. For a computer presentation, a dark background may be preferable; Figure 12.20 shows the PivotChart with a dark background style selected.
FIGURE 12.20 A PivotChart with contrasting color bars and a dark background.
NOTE
NEW FEATURE
The four styles you most recently selected appear in the top row of the Chart Styles group, so they are always available for one-click selection.
Excel 2007 has a new feature: Chart templates. However, these templates only work with standard charts, not PivotCharts, so unfortunately they are no use when creating PivotCharts.
The bar charts produced in this section are only a small selection of the PivotChart types you can produce in Excel, based on Access data. The six most popular chart types are shown in the Charts group of the Insert tab on the Ribbon, shown in Figure 12.21.
423
Part II
To see all the available chart types, click the Other Charts command, then the All Chart Types command at the bottom of the drop-down chart palette, as shown in Figure 12.22.
FIGURE 12.22 Selecting the All Charts command to see all the Excel chart types.
The Create Chart dialog opened by the All Charts command is shown in Figure 12.23.
424
12
Though most chart types are available in both Access and Excel, there are some differences: The Polar chart type is only available in Access, and the Surface chart type is only available in Excel. Figure 12.24 shows the Access chart types on the left, and the Excel chart types on the right, to make it easier to identify the chart type you want to use.
425
Part II
426
12
If you click the Yes button on this dialog, the Edit Amounts form will open, as shown in Figure 12.27, where you can edit product amounts as needed.
427
Part II
On the Shipping Reports and Reordering form, the 2x Reorder button sets the Reorder Amount to twice the Reorder Level for all records; the Zero Reorder button sets the Reorder Amount to zero for all records. You can also manually edit the Reorder Amount for any product as desired. After setting the Reorder Amount as desired for all records, click the Send Shipping Reports button to save the Shipping report (rptShipping) as a PDF file, update field values in tblProducts as needed, and create a new email message with the report file attached to it, ready to email to the appropriate address (if the report is always sent to the same person or department, the email address could be hard-coded). Figure 12.28 shows the shipping report.
NEW FEATURE
Although saving to the PDF format didnt make it to the release version of Access 2007, Microsoft has provided a downloadable utility that adds PDF support to Access. This utility can be downloaded from http://www.microsoft.com/downloads/details.aspx?familyid= F1FC413C-6D89-4F15-991B-63B07BA5F2E5&displaylang=en (or search the Microsoft Downloads page for Save to PDF). Once you have downloaded and installed it, you will see a new Save to PDF selection on the Save As submenu of the File menu, as shown in Figure 12.29, and you can use the acFormatPDF named constant as the value of the outputfile argument of the OutputTo method to create a PDF file, as in the code sample below.
428
12
FIGURE 12.29 The new PDF selection for saving a database object.
429
Part II
FIGURE 12.30 An email message with an attached PDF file created from an Access report.
Clicking the Send Reorder Requests button works similarly; it creates a PDF file from the Products to Reorder report and emails it as an attachment. Figure 12.31 shows the Products to Reorder report.
430
12
This error handler skips to the CreateSnapshot section if there is an error on the line that outputs the file to the PDF format.
On Error GoTo CreateSnapshot strCurrentPath = Application.CurrentProject.Path strReport = rptProductsToReorder
First try to export the Products to Reorder report to PDF (this will only work if you have installed the Save to PDF utility)
strReportFile = strCurrentPath & \Products To Reorder.pdf Debug.Print Report and path: & strReportFile DoCmd.OutputTo objecttype:=acOutputReport, _ objectname:=strReport, _ outputformat:=acFormatPDF, _ outputfile:=strReportFile
If the PDF file was created successfully, go to the CreateEmail section, skipping the CreateSnapshot section of code.
GoTo CreateEmail On Error GoTo ErrorHandler CreateSnapshot:
Create an Outlook email message, fill in its subject, and attach the PDF or snapshot file to the message:
Set appOutlook = GetObject(, Outlook.Application) Set msg = appOutlook.CreateItem(olMailItem) msg.Attachments.Add strReportFile msg.Subject = Products to reorder for _ & Format(Date, dd-mmm-yyyy) msg.Save
431
Part II
Ask for confirmation to set all ReorderAmount values to zero, and add the amount ordered to UnitsOnOrder:
strTitle = Confirmation strPrompt = Clear reorder and on order amounts? intReturn = MsgBox(strPrompt, vbQuestion + vbYesNo, _ strTitle) If intReturn = vbYes Then DoCmd.SetWarnings False strSQL = UPDATE qryProductsToReorder SET _ & qryProductsToReorder.UnitsOnOrder = _ & [UnitsOnOrder]+[ReorderAmount], _ & qryProductsToReorder.ReorderAmount = 0; Debug.Print SQL string: & strSQL DoCmd.RunSQL strSQL End If
Display the Outlook email message with the PDF or snapshot attachment:
msg.Display DoCmd.Close acForm, Me.Name ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Sub
Summary
The techniques described in this chapter should give you more ideas about how you can use Word, Excel, and Outlook to expand the functionality of Access databases, using VBA code to examine data and make decisions about what data should be exported, and to format the Office documents filled with Access data.
432
ffice 2000 introduced COM add-ins as a new development tool, an alternative to creating VBA add-ins for Access, Excel, Outlook, and Word. A COM add-in is created as a Dynamic Link Library (DLL) that is registered to work with Office applications. COM add-ins (at least theoretically) can be written to work with multiple Office programs, though realistically, because of the differences in functionality between Access, Word, Outlook, and Excel, only very simple COM add-ins of the Hello, World! type can actually be designed to work across multiple Office applications. If you bought the Developer Edition of Office 2000 (or later, Office XP) you could create COM add-ins in the Access Visual Basic window, using its support for opening and editing VBA projects, although it wasnt easy because of the lack of debugging support. There was no Developer Edition of Office 2003, and there is none for Office 2007, so that option is no longer viable, unless you still have the Developer Edition of Office 2000 or Office XP installed.
IN THIS CHAPTER
Creating COM add-ins with Visual Basic 6 Installing and troubleshooting COM add-ins Comparing COM add-ins and Access add-ins
CROSS-REF
Visual Studio Tools for Office lets you create Visual Studio add-ins for some Office components, but unfortunately, even the latest edition, the one that supports Office 2007, still lacks support for creating Access add-ins. See Chapter 16 for a discussion of creating Visual Studio add-ins for working with the Access 2007 Ribbon.
However, that doesnt mean you cant create COM add-ins for Office 2007. Visual Basic was last updated in 1998 (v. 6.0), but it is still quite useful, and is fully supported by Microsoft, unlike most other Microsoft applications of that vintage. If you have been working with VB 6 for years, you dont have to put aside your hard-won expertise and start learning Visual Studio 2005; you can create COM add-ins that will work in Office 2007 using VB 6. (If you do want to learn how to create add-ins with Visual Studio 2005, see Chapter 16.)
435
Part III
In earlier versions of Access, COM add-ins placed buttons on the menu or toolbar you specified, using the CommandBars collection. In Access 2007, COM add-ins place buttons in the Toolbar Commands group of the Add-Ins tab of the Ribbon, for backwards compatibility with the old CommandBars collection. In addition to COM add-ins and Access add-ins, Access 2007 also offers a brand-new option: using the XML programming language to add controls to the Ribbon, powered by code written in VBA. This technique is covered in Chapter 15.
436
13
FIGURE 13.1 Copying the COM Add-In project files into the VB Projects folder.
437
Part III
FIGURE 13.3 A new VB project created from the COM Add-in project template.
To set a reference in a VB 6 project, drop-down the Project menu and select References, as shown in Figure 13.4.
NOTE
For best functioning, a COM add-in designed to run in Access should have a reference set to the Access and (if needed) DAO object libraries, as shown in Figure 13.5. If you create your own COM add-in from scratch, you will probably need to set one or both of these references; I have set them in the sample VB template project, so they are already checked in projects made from this template.
438
13
FIGURE 13.5 References set to the Access and DAO object libraries in the VB References dialog.
You can now modify the form (if you need a form in your add-in), and the code in the standard module and designer, to create a custom COM add-in for use in Access:
1. First open the designer and fill in the basic information about the add-in on its General tab (the Advanced tab rarely needs to be modified from its default settings). 2. Enter the add-ins display name and description in the Addin Display Name and Addin Description boxes, then select the Office application (Access in this case) from the Application drop-down list (see Figure 13.6). The currently running Office version is automatically selected in the Application Version box. 3. Finally, select the add-ins load behavior from the Initial Load Behavior drop-down Startup is the appropriate choice if you want the add-in to always be available in any Access database (which is the usual case).
The SharedCode module (as its name suggests) contains code that applies to the entire add-in, such as an error handler and the standard code that creates and removes the add-ins toolbar buttons or menu items (for the rare case where a multi-application COM add-in puts a command on the same menu or toolbar in each Office application). The code in the Designer (which I named AccessDesigner to indicate that it is an Access Designer) contains code specific to Access. (If you are creating a multi-application add-in, you need to create one Designer for each Office application that your add-in supports). Some of the standard code in the Designer works with the frmCOMAddIn form; if your add-in doesnt need to display a form, you can delete or comment out any code that references this form, but it is a good idea to leave all the procedures in the module, in case you might need them later on.
439
Part III
Figure 13.6 Entering the COM add-ins identifying information and other options in the Designer dialog.
The Designer code includes the procedures that implement the add-ins functionality; they require little (if any) modification from those that would run in an Access add-in.
CROSS-REF
For more details on the LNC Rename add-in, see my book Expert One-on-One Microsoft Access Application Development (ISBN: 0764559044).
440
13
I added the StripChars function to this module; it is called throughout the add-in to remove various characters and spaces from control names during renaming, so as prevent problems when the controls are referenced in code. I modified the standard template AddInErr procedure slightly; it creates a message box string that is called from error handlers in the add-in. The SharedCode module that contains these procedures is listed next:
Option Explicit Public Function StripChars(strText _ As String) As String
strStripChars = `~!@#$%^&*()-_=+[{]};:,<.>/? _ & Chr$(34) & Chr$(13) & Chr$(10) strTestString = strText i = 1 Do While i <= Len(strTestString)
441
Part III
Public Sub AddInErr(errX As ErrObject) Displays message box with error information Dim strMsg As String strMsg = _ An error occurred in the & App.Title _ & vbCrLf & Error #: & errX.Number _ & vbCrLf & Description: & errX.Description MsgBox strMsg, , Error! End Sub
The standard designer code needs some modifications to enable it to work with add-in events that fire when the add-in is loaded or unloaded, or when the host application (Access in this case) starts or shuts down. These events are implemented via the IDTExtensibility2 library, using the Implements line at the beginning of the module.
442
13
The procedures starting with Private Sub IDTExtensibility2 are the events supported by this library. You need to have all five event procedures in the Designer module, even though your add-in may not use all of them. The ones I dont need have only a comment line:
No code needed, but must have the event stub
COM add-ins generally put one or more buttons or commands on a toolbar or menu in the host application; each one requires a WithEvents statement in the Declarations section of the Designer module, to support code on the Click event of the button or command. My LNC Control Renaming code has two such events, to support two toolbar buttons. The remainder of the Declarations section contains groups of public and private variables for use in various add-in procedures. The procedures in this module have the functionality described next: n The OnConnection event procedure sets the command bar button variables, and uses the CreateFormCommandBarButton and CreateReportCommandBarButton functions to create the buttons on the Form Design (or Report Design) Access toolbars (in older versions of Access), or in the Toolbar Commands group on the backward compatibility Add-Ins tab of the Ribbon (in Access 2007 running on Windows XP). n The OnDisconnection event procedure runs a function that removes the two command bar buttons when the add-in is disconnected by unloading the add-in from the COM Add-Ins dialog (they are not removed when Access is closed). n The two Click event procedures run the LNCRenameFormControls and LNCRenameReportControls functions, which respectively rename form and report controls.
NOTE
An Access 20002003 command bar button has its OnAction property set to the name of a macro (a Sub procedure with no arguments) that is run when the button is clicked; the syntax is different for buttons placed on command bars from a COM add-in. Instead, the OnAction property is set to the ProgId of the COM add-in, and the buttons Click event is handled by the Click event procedure in the Designer module.
n The CreateFormCommandBarButton creates the Access toolbar button that renames controls on open forms. The function first sets the pappAccess variable to the Access.Application object, then sets a reference to the Form Design toolbar (where the button will be placed), looking for an existing button on this toolbar, using its Tag property, and creating it if it does not already exist. n The CreateReportCommandBarButton procedure does a similar job for the Report Design toolbar button that renames report controls. n The RemoveAddInCommandBarButton function (called by the OnDisconnection event procedure) removes the add-ins command buttons. n The LNCRenameFormControls and LNCRenameReportControls functions are basically similar to the code in the Access LNC Rename add-in, so I will not discuss them in detail. The main difference is that the COM add-in functions rename controls on the open forms (or reports) only; the Rename Form Controls and Rename Report Controls menu add-ins rename controls on all forms or reports, whether or not they are open.
443
Part III
The AccessDesigner code is listed next, with the specific modifications needed to implement the add-ins functionality, using the event procedures supported by the IDTExtensibility2 library to create command bar buttons, and assign procedures to them. The module also contains the procedures used to do control renaming:
Implements IDTExtensibility2 Private WithEvents frmcbb As Office.CommandBarButton Private WithEvents rptcbb As Office.CommandBarButton
444
13
Private Sub IDTExtensibility2_OnAddInsUpdate(custom() _ As Variant) On Error GoTo ErrorHandler No code needed, but must have the event stub ErrorHandlerExit: Exit Sub ErrorHandler: AddInErr Err Resume ErrorHandlerExit End Sub
Private Sub IDTExtensibility2_OnBeginShutdown(custom() _ As Variant) On Error GoTo ErrorHandler No code needed, but must have the event stub ErrorHandlerExit: Exit Sub ErrorHandler: AddInErr Err Resume ErrorHandlerExit End Sub Private Sub IDTExtensibility2_OnConnection(ByVal _ Application As Object, ByVal ConnectMode _ As AddInDesignerObjects.ext_ConnectMode, _ ByVal AddInInst As Object, custom() As Variant)
445
Part III
Calls shared code to create a new command bar button to rename controls on a form or report.
On Error GoTo ErrorHandler Set frmcbb = CreateFormCommandBarButton(Application, _ ConnectMode, AddInInst) Set rptcbb = CreateReportCommandBarButton(Application, _ ConnectMode, AddInInst) ErrorHandlerExit: Exit Sub ErrorHandler: AddInErr Err Resume ErrorHandlerExit End Sub
Private Sub IDTExtensibility2_OnDisconnection(ByVal _ RemoveMode As AddInDesignerObjects.ext_DisconnectMode, _ custom() As Variant) On Error GoTo ErrorHandler
Private Sub IDTExtensibility2_OnStartupComplete(custom() _ As Variant) On Error GoTo ErrorHandler No code needed, but must have the event stub ErrorHandlerExit: Exit Sub
446
13
Private Sub frmcbb_Click(ByVal ctl As _ Office.CommandBarButton, CancelDefault As Boolean) On Error Resume Next Call LNCRenameFormControls End Sub
Private Sub rptcbb_Click(ByVal ctl As _ Office.CommandBarButton, CancelDefault As Boolean) On Error Resume Next Call LNCRenameReportControls End Sub Public Function CreateFormCommandBarButton(ByVal _ Application As Object, ByVal ConnectMode _ As AddInDesignerObjects.ext_ConnectMode, _ ByVal AddInInst As Object) As Office.CommandBarButton On Error GoTo ErrorHandler
Store a reference to the Application object in a public variable so other procedures in the add-in can use it.
Set pappAccess = Application
Add a button to call the add-in from the command bar, if it doesnt already exist. Look for the button on the command bar.
Set cbbAddIn = _ cbrMenu.FindControl(Tag:=Rename Form Controls) On Error Resume Next If cbbAddIn Is Nothing Then
447
Part III
Store a reference to the Application object in a public variable so other procedures in the add-in can use it.
Set pappAccess = Application
448
13
Add a button to call the add-in from the command bar, if it doesnt already exist. Look for the button on the command bar.
Set cbbAddIn = _ cbrMenu.FindControl(Tag:=Rename Report Controls) If cbbAddIn Is Nothing Then
449
Part III
This procedure removes the command bar buttons for the add-in if the user disconnected it.
On Error Resume Next
If the user unloaded the add-in, remove the button. Otherwise, the add-in is being unloaded because the application is closing; in that case, leave button as is.
If RemoveMode = ext_dm_UserClosed Then
Generate table of control types to use in renaming controls (if it does not already exist).
CreateCTTable On Error GoTo ErrorHandler
450
13
Determine whether original the control names should be stored in the Tag property.
pstrMessage = _ When processing form controls, should the original _ & control name be saved to the controls Tag _ & property? intTag = MsgBox(pstrMessage, vbYesNo + vbQuestion + _ vbDefaultButton2, Control Name Backup) If intTag = vbYes Then blnTag = True Else blnTag = False End If
451
Part III
Case acListBox strPrefix = lst i = ControlCS(pctl, strPrefix, blnTag) Case acOptionGroup strPrefix = fra i = ControlCS(pctl, strPrefix, blnTag) Case acOptionButton strPrefix = opt strControlSource = pctl.ControlSource If blnUnbound = False Then i = ControlCS(pctl, strPrefix, blnTag) Else i = ControlNA(pctl, strPrefix, blnTag) End If
452
13
Case acLine strPrefix = lin i = ControlNA(pctl, strPrefix, blnTag) Case acPage strPrefix = pge i = ControlNA(pctl, strPrefix, blnTag) Case acPageBreak strPrefix = brk i = ControlNA(pctl, strPrefix, blnTag) Case acRectangle strPrefix = shp i = ControlNA(pctl, strPrefix, blnTag) End Select Next pctl Next pfrm Call MsgBox(All form controls renamed!, _ vbOKOnly, Done) ErrorHandlerExit: Exit Function ErrorHandler:
If an option button or checkbox is unbound, set blnUnbound to True so the code uses the NA function instead of CS.
If Err.Number = 2455 Then blnUnbound = True Resume Next Else AddInErr Err Resume ErrorHandlerExit End If End Function
[I am omitting the LNCRenameReportControls function from this listing, because it is substantially similar to the LNCRenameFormControls function.] The following procedures rename form and report controls of various types. Controls are grouped depending on whether or not they are bound, and other relevant properties. Each group of controls (ControlCS, ControlCA, and so forth) needs different code to create an appropriate name for
453
Part III
the control. The bound controls, for example, create a name using the name of the bound field; labels create a name using caption text, and so forth:
Public Function ControlCS(ctl As Access.Control, _ strPrefix As String, blnTag As Boolean) As Integer
Does group renaming of all controls with control sources on a form or report.
On Error GoTo ErrorHandler Dim strControlSource As String strControlSource = Nz(ctl.ControlSource) pstrOldCtlName = ctl.ControlName
Check whether control already is correctly named and also special case for controls whose original name starts with Option or Frame (same first three letters as prefix).
If Left(pstrOldCtlName, 3) Left(pstrOldCtlName, 6) Left(pstrOldCtlName, 3) Left(pstrOldCtlName, 5) GoTo ErrorHandlerExit = strPrefix And _ <> Option And _ = strPrefix And _ <> Frame Then
Show the user n the original control name n the control type n control source n proposed new name
454
13
If the proposed control name is already in use, return to the renaming dialog.
pintRenameFail = True If Err.Number = 2104 Then MsgBox There is another control named & _ pstrNewCtlName & ; please try again, , _ Control Name Used pstrNewCtlName = pstrNewCtlName & 1
455
Part III
Else AddInErr Err Resume ErrorHandlerExit End If Resume Next End Function
456
13
ElseIf Right(pstrNewCtlName, 5) = Label Then pstrNewCtlName = Left(pstrNewCtlName, _ Len(pstrNewCtlName) - 5) End If pintRenameFail = True Do While pintRenameFail pintRenameFail = False pintReturn = MsgBox(Rename _ & DLookup([ControlTypeName], _ zLNCtblControlType, [ControlType] = _ & ctl.ControlType) _ & control currently named & pstrOldCtlName _ & vbCrLf & (caption: & strCaption & ) to _ & vbCrLf & pstrNewCtlName & ?, vbYesNo + _ vbQuestion + vbDefaultButton1, Rename control) If pintReturn = vbYes Then If blnTag = True Then ctl.Tag = ctl.ControlName ctl.ControlName = pstrNewCtlName ElseIf pintReturn = vbNo Then pstrNewCtlName = _ InputBox(Modify new control name, _ Rename control, pstrNewCtlName) ctl.ControlName = pstrNewCtlName End If Loop ErrorHandlerExit: Exit Function ErrorHandler:
If the proposed control name is already in use, return to the renaming dialog.
pintRenameFail = True If Err.Number = 2104 Then MsgBox There is another control named & _ pstrNewCtlName & ; please try again, , _ Control Name Used pstrNewCtlName = pstrNewCtlName & 1 Else AddInErr Err Resume ErrorHandlerExit End If Resume Next End Function
457
Part III
Does group renaming of all controls with source objects on a form or report.
Called from RenameFormControls and RenameReportControls in this module On Error GoTo ErrorHandler pstrOldCtlName = ctl.ControlName pstrSourceObject = Nz(ctl.SourceObject) If Left(pstrOldCtlName, 3) = strPrefix Then Exit Function ElseIf pstrSourceObject <> Then If Left(pstrSourceObject, 3) = frm Then pstrNewCtlName = strPrefix & _ Mid(StripChars(pstrSourceObject), 4) ElseIf Left(pstrSourceObject, 4) = fsub Then pstrNewCtlName = strPrefix & _ Mid(StripChars(pstrSourceObject), 5) Else pstrNewCtlName = strPrefix & _ StripChars(pstrSourceObject) End If ElseIf pstrSourceObject = Then If Left(pstrOldCtlName, 3) = frm Then pstrNewCtlName = strPrefix & _ Mid(StripChars(pstrOldCtlName), 4) ElseIf Left(pstrOldCtlName, 4) = fsub Then pstrNewCtlName = strPrefix & _ Mid(StripChars(pstrOldCtlName), 5) Else pstrNewCtlName = strPrefix & _ StripChars(pstrOldCtlName) End If Else pstrNewCtlName = strPrefix & _ StripChars(pstrOldCtlName) End If If Right(pstrNewCtlName, 7) = Subform Then pstrNewCtlName = Left(pstrNewCtlName, _ Len(pstrNewCtlName) - 7) End If pintRenameFail = True Do While pintRenameFail pintRenameFail = False pintReturn = MsgBox(Rename _ & DLookup([ControlTypeName], _ zLNCtblControlType, [ControlType] = _
458
13
& ctl.ControlType) _ & control currently named & pstrOldCtlName _ & vbCrLf & (source object: & pstrSourceObject _ & ) to & vbCrLf & pstrNewCtlName & ?, vbYesNo _ + vbQuestion + vbDefaultButton1, Rename control) If pintReturn = vbYes Then If blnTag = True Then ctl.Tag = ctl.ControlName ctl.ControlName = pstrNewCtlName ElseIf pintReturn = vbNo Then pstrNewCtlName = _ InputBox(Modify new control name, _ Rename control, pstrNewCtlName) ctl.ControlName = pstrNewCtlName End If Loop ErrorHandlerExit: Exit Function ErrorHandler:
If the proposed control name is already in use, return to the renaming dialog.
pintRenameFail = True If Err.Number = 2104 Then MsgBox There is another control named & _ pstrNewCtlName & ; please try again, , _ Control Name Used pstrNewCtlName = pstrNewCtlName & 1 Else AddInErr Err Resume ErrorHandlerExit End If Resume ErrorHandlerExit End Function
Does group renaming of all controls not fitting the other categories on a form or report.
Called from RenameFormControls and RenameReportControls in this module On Error GoTo ErrorHandler pstrOldCtlName = ctl.ControlName
459
Part III
Special case for lines whose default name is Line or Option (same first three letters as the standard prefix).
If Left(pstrOldCtlName, 3) = strPrefix And _ Left(pstrOldCtlName, 6) <> Option And _ Left(pstrOldCtlName, 4) <> Line Then Exit Function Else pstrNewCtlName = strPrefix _ & StripChars(pstrOldCtlName) End If pintRenameFail = True Do While pintRenameFail pintRenameFail = False pintReturn = MsgBox(Rename & _ DLookup([ControlTypeName], _ zLNCtblControlType, [ControlType] = _ & ctl.ControlType) & control currently named _ & pstrOldCtlName & to & vbCrLf _ & pstrNewCtlName & ?, vbYesNo + vbQuestion _ + vbDefaultButton1, _ Rename control) If pintReturn = vbYes Then If blnTag = True Then ctl.Tag = ctl.ControlName ctl.ControlName = pstrNewCtlName ElseIf pintReturn = vbNo Then pstrNewCtlName = _ InputBox(Modify new control name, _ Rename control, pstrNewCtlName) ctl.ControlName = pstrNewCtlName End If Loop ErrorHandlerExit: Exit Function ErrorHandler:
If the proposed control name is already in use, return to the renaming dialog.
pintRenameFail = True If Err.Number = 2104 Then MsgBox There is another control named & _ pstrNewCtlName & ; please try again, , _ Control Name Used pstrNewCtlName = pstrNewCtlName & 1 Else AddInErr Err
460
13
Public Function CreateCTTable() Called from LNCRenameFormControls and LNCRenameReportControls function in this module Dim strCTTable As String strCTTable = zLNCtblControlType
Generate the table of control types to use in renaming controls. If there is a table not found error, exit function.
pstrSQL = CREATE TABLE & strCTTable & _ (ControlType LONG, ControlTypeName TEXT (50)); DoCmd.RunSQL pstrSQL
461
Part III
!ControlType = 103 !ControlTypeName = .Update .AddNew !ControlType = 104 !ControlTypeName = .Update .AddNew !ControlType = 105 !ControlTypeName = .Update .AddNew !ControlType = 106 !ControlTypeName = .Update .AddNew !ControlType = 107 !ControlTypeName = .Update .AddNew !ControlType = 108 !ControlTypeName = .Update .AddNew !ControlType = 109 !ControlTypeName = .Update .AddNew !ControlType = 110 !ControlTypeName = .Update .AddNew !ControlType = 111 !ControlTypeName = .Update .AddNew !ControlType = 112 !ControlTypeName = .Update .AddNew !ControlType = 114 !ControlTypeName = .Update .AddNew !ControlType = 118 !ControlTypeName = .Update .AddNew
Image
Command Button
Option Button
Check Box
Option Group
Text Box
List Box
Combo Box
Subform/Subreport
Object Frame
Page Break
462
13
!ControlType = 122 !ControlTypeName = Toggle Button .Update .AddNew !ControlType = 123 !ControlTypeName = Tab Control .Update .AddNew !ControlType = 124 !ControlTypeName = Page .Update .Close End With ErrorHandlerExit: Exit Function ErrorHandler: If Err.Number = 3010 Then
TIP
To rename a VB project, select the project (the top line in the Project Explorer) and modify its name property in the properties sheet. To modify a Designers name, open it, then select it and modify its name in the properties sheet. The name you give a VB project is the one that will be used by default when creating a DLL.
463
Part III
FIGURE 13.8 A COM add-in button on the Add-Ins tab of the Ribbon in Access 2007.
If you dont see your COM add-ins button(s) in an Access database after copying the DLL file to the Add-ins folder, you can install the add-in from the COM Add-Ins dialog, which can be opened from the Add-ins page of the Access Options dialog. To install a COM add-in manually, do the following:
1. For Windows Vista only, run Access as an administrator by right-clicking the MSACCESS.EXE file in the Office 12 subfolder under the Microsoft Office folder, and selecting Run as administrator from its right-click context menu. 2. In an Access database, click the Office button, then click the Access Options button at the lower right, as shown in Figure 13.9.
3. Select the Add-ins page on the Access Options dialog to see your installed add-ins (youll see both Access and COM add-ins listed there, plus one or more add-ins that are installed with Office). If your add-in is listed in the Inactive Application Add-Ins group, you will need to install it in the COM Add-ins dialog.
464
13
4. To install or uninstall COM add-ins, select COM Add-ins in the drop-down list at the bottom of the screen, as shown in Figure 13.10.
5. On clicking OK, you will see the old COM Add-Ins dialog, the same as in Access 2003, as shown in Figure 13.11.
465
Part III
6. The name displayed in the Add-Ins available list is the one entered as the add-ins name in the Designer, but curiously, the description entered into the Designers Description field does not appear on this dialog. 7. If your COM add-in doesnt appear in the list of available add-ins, click the Add button to browse for it; after locating it, click OK in the Add Add-In dialog, as shown in Figure 13.12.
8. The COM add-in should now appear in the COM Add-Ins dialog. You can check its checkbox (if needed) and close the dialog; its button(s) should then appear on the AddIns tab of the Ribbon, in the appropriate context; for example, a button intended to display on the Form Design toolbar (like the one shown in Figure 13.6) will appear when you have a form open in design view.
NOTE
In Access 2000 through 2003, COM add-in buttons appeared on the designated toolbar or menu; in the case of this add-in, that would be the form or report design toolbar. In Access 2007, all the buttons appear directly on the Toolbar Commands group on the Add-Ins tab. If you are running Windows Vista, you wont see the Add-Ins tab unless you run Access as an administrator, as described in Step 1 above.
You can clear the add-ins checkbox to temporarily unload it, or you can select it and click Remove to uninstall it completely.
466
13
To reopen a COM add-in for editing, double-click the .vbp (VB project) file. You need to have any supporting files (.bas, .dsr, and .frm) in the same folder as the .vpb file, because they are all part of the project. If your COM add-in isnt behaving as you expect, first unload it (from the COM Add-ins menu), then close Access and reopen a database to see if it now works. This can fix problems that may occur when an old version of the COM add-in code is being run instead of the current version.
WARNING
If you have a database open with the COM add-in loaded, you will get a Permission denied message when trying to make the DLL, because the DLL is being used. Close any open databases and try again, and you should be able to save the modified DLL.
Next, check for duplicates of the DLL file (perhaps backup copies) that might be running instead of the current version of the DLL. In my experience, even DLLs not located in the AddIns folder may be run, so its best to have only one DLL on your computer for any given add-in (the latest version). Backup copies can be transferred to another computer, to a disc, or zipped, to prevent confusion. If you find that code referencing Access objects isnt running (with no error message, or an inappropriate error message, such as No Forms Open when you have forms open), you may need to add a specific Access application reference to your code. Specifically, whereas COM add-in code in an Access Designer using just Forms to reference the Access Forms collection, or Reports to reference the Reports collection of a database, ran fine in earlier versions of Office, in Office 2007 the Access application variable pappAccess must be used, so the current syntax needs to be pappAccess.Forms instead of just Forms. If you change the name of a toolbar button created by a COM add-in, you may see the old button on the toolbar, instead of (or in addition to) the new one. To remove the old button, add a line of code to the RemoveAddInCommandBarButton function to remove the button, using the buttons old name instead of Old Control Name:
.Controls(Old Control Name).Delete
Create a new DLL, open an Access database to load the add-in, then unload the add-in from the COM Add-Ins dialog to run the code with the extra line once. Close Access, reopen the VB project, delete the line, and re-create the DLL. That should get rid of the old button.
467
Part III
Saving the original control name to the Tag property can occasionally be useful, especially when you are renaming controls in a database created by someone else, and you may need to know the original control name in order to fix a reference later on. However, the default choice is No, because mostly there is no need to save the original control name. After selecting a choice, the code then proceeds to cycle through the open forms and, for each form, cycle through its controls. For any control that doesnt have the appropriate control prefix, a new name is created, and presented for approval in a message box, as shown in Figure 13.14.
Generally, the new name can be accepted as is; occasionally (for example, for labels with very lengthy captions, or controls with expressions), the new name needs to be edited, which is done by clicking No and then editing the control name in an InputBox. Even in Access 2007, when you create a new bound form using the Form button in the Create tab of the Ribbon, all bound controls will have the same names as their fields, which can lead to circular reference errors when running code. Thus, it is a good idea to run the Rename Form Controls (or Rename Report Controls) command immediately after creating a bound form or report, before writing any code that references its fields or controls.
468
13
CROSS-REF
You can add groups, buttons, and menu selections to the Ribbon using XML; this technique is discussed in Chapter 15.
Compared to COM add-ins, Access add-ins have several extra features: you can create not only menu add-ins, but also wizards of various types, and builders; this lets you add functionality to different locations in an Access database. In Access 2000 through 2003, you could create wizards that would appear as extra choices on the New Form dialog, though in Access 2007 this must now be done using XML to modify the Ribbon. However, property builders that run from various properties still work fine in Access 2007 (at least if you are running Windows XP), so my LNC Rename add-in can be run from the Name property of a control to rename an individual control; this functionality cant be duplicated in a COM add-in.
Summary
In this chapter you learned how to create a VB 6 COM add-in that works with Access 2007, placing buttons in the Toolbar Commands group of the Add-Ins tab of the Ribbon. If you have a VB 6 add-in created in a previous version of Office, you can modify it slightly so that it will work in Access 2007, reusing your code. If, on the other hand, you want to learn a new programming language so you can put groups and buttons on specific tabs of the Ribbon, see the next two chapters for working with Ribbon XML and Visual Studio 2005 add-ins.
469
ost of the sample databases for earlier chapters included objects from the Access 2007 Backup database, used to make incrementally numbered database backups. In order to use this feature in a database, you need to import several objects from Access 2007 Backup.accdb into the current database, and set a reference to the Microsoft Scripting Runtime library, which is a nuisance. It would be much more convenient to just have backup available in all your Access databases, say from a menu command. An Access add-in will do just that, encapsulating a set of database objects and code into a single package that is available to all Access databases. In this chapter I use as an example an Access add-in (Extras 2007.accda, that includes an enhanced version of the Backup code (from basBackup in Access 2007 Backup.accdb), with some enhancements: a setup form for specifying the backup folder; and a set of objects and code that let you print out lists of tables or queries, and their fields, excluding those with user-specified prefixes very handy for when you need to know which fields are in which tables during database development, or for documenting the database structure.
IN THIS CHAPTER
Creating Access menu add-ins Creating Access wizards Creating Access property builders Special considerations and troubleshooting for Access add-ins
NOTE CAUTION
If you are attempting to install an add-in in Access 2007 running on Windows Vista, you may get the security warning shown in Figure 14-1. This is probably because you are not running Access as an administrator. To run Access as an administrator, right-click the MSACCESS.EXE file in the Office 12 subfolder under the Microsoft Office folder, and select Run as administrator, then open an Access database and install the add-in. This is not a problem when installing add-ins for Access 2007 running on Windows XP.
471
Part III
FIGURE 14.1 A security warning when attempting to install an Access add-in for Access 2007 running on Windows Vista.
NOTE
Access add-ins created in earlier versions of Access (as .mda library databases) will generally run in Access 2007 running on Windows XP, at least if they dont have conflicts with the new interface. For example, my LNC Rename.mda add-in, which renames database objects and controls according to the Leszynski Naming Convention, works fine in Access 2007, although it doesnt process controls bound to fields of the new Attachment data type. An older add-in that creates custom menu bars, however, will definitely have problems, because the new Ribbon replaces the old command bars interface. In Windows Vista, security features currently prevent add-ins that create Wizards or Property Builders from running (this problem is scheduled to be fixed in an upcoming Service Patch).
Add-in Types
There are three types of Access add-ins, with several subtypes, as listed in Table 14.1. All these types of add-ins are stored as wizards in the Registry. Sometimes you will see a Builder or Menu add-in referred to as a wizard, but I will reserve the term wizard for the add-ins that are invoked when a new object is created, as listed in the Wizard column of Table 14.1.
472
14
TABLE 14.1
Called when a new table, query, form, report, or control is created Subtypes Table Wizards Query Wizards Form Wizards Report Wizards Control Wizards
473
Part III
3. On the Access Options dialog, select the Current Database section, as shown in Figure 14.3.
FIGURE 14.3 The Current Database section of the Access Options dialog box.
474
14
4. Click the Navigation Options button to open the Navigation Options dialog, where finally you can check the Show System Objects checkbox, as shown in Figure 14.4.
FIGURE 14.4 Checking the Show System Objects checkbox on the Navigation Options dialog.
5. Click OK to close the Navigation Options dialog, and again to close the Access Options dialog.
In the Tables section of the Navigation Pane, you will now see a number of system tables, starting with the prefix MSys, and displayed in a dimmed font (Figure 14.5). If you imported the USysRegInfo table from another database, you will see it there too, although curiously, it is not dimmed. You dont have to do anything special to make the USysRegInfo table a system table; a table with this name is automatically categorized as a system table. If you are creating the table from scratch, refer to Table 14.2 for a listing of the necessary fields and their data types.
475
Part III
TABLE 14.2
Subkey
Text
The name of the Registry subkey where a specific Registry setting is stored; can be either HKEY_CURRENT_ACCESS_PROFILE or HKEY_LOCAL_MACHINE. For Access add-ins, the HKEY_CURRENT_ACCESS_PROFILE is preferable, because it automatically uses the Registry section for the running version of Access, allowing the same add-in to work in multiple Access versions. The type of entry to create; can be key (0), string (1), or DWORD (4) The name of the Registry value The value of the Registry value
NOTE
If you create the USysRegInfo table from scratch, you will get an error when entering the name Value for the last field (as shown in Figure 14.6). This is because Microsoft has violated its own rules by giving a field a name that is a reserved word (in this case, a property name). However, you can save the field, and it does work. Dont change the field name, because each field in this table must have a specific name to work correctly.
476
14
FIGURE 14.6 An error when creating the Value field in the UsysRegInfo table.
If you copy the USysRegInfo table from another add-in, it has a default value of GenUniqueID() for the Type field, and also has some useful information in the Description column telling you the value needed for each field data type, as shown in Figure 14.7.
Each type of add-in requires a set of records in the USysRegInfo table, as described in the following sections. The Extras 2007 add-in that is the sample add-in for this chapter is a set of menu add-ins, which will be described in detail; for the other add-in types I will reference two add-ins that I created in earlier versions of Access.
NOTE
You might think that add-ins would be found on the Add-Ins tab of the Ribbon in Access 2007 (this tab may not be visible if you are running Windows Vista). But this is not the case. Your Access add-ins are located on the Add-ins menu of the Database Tools tab. The Add-Ins tab has a set of menus like those in previous versions of Access, except that they dont work. The purpose of these non-functional menus is to display commands placed on the Access 2003 (or earlier) menus by add-ins working with the CommandBars collection. This is a very awkward method of implementing backwards compatibility; if you have such an add-in, you will probably want to redo it to place commands on various tabs of the Ribbon, as described in Chapter 15.
477
Part III
Menu Add-ins
A menu add-in needs three rows in the USysRegInfo table, each with an appropriate value in the Type field, and some with values in the ValName or Value field as well, as described in detail next: n In the first row of the add-in, the Subkey field (which is the same for all the add-ins rows) has the Registry key information, referencing the Menu Add-Ins section under the HKEY_CURRENT_ACCESS_PROFILE key (which references the currently running version of Access), and ending with the command name to display on the Add-Ins menu, with an ampersand if desired to make a hot key. The Type field has a value of 0, indicating the start of a new add-in. The ValName and Value fields are blank. n In the second row of the add-in, the Type field has a value of 1, the ValName field has the value Library, and the Value field has the location and name of the library database, using the |ACCDIR\ placeholder to point to the AddIns folder (in earlier versions of Access, this was the Access folder itself, which explains the name). n In the add-ins third row, the Type field has a value of 1, the ValName field has the value Expression, and the Value field has the name of function to run (preceded by an equals sign, and followed by a pair of parentheses). In Table 14.3, which lists the three rows for the Back up Database command on the AddIns menu, the italicized text is the information specific to this add-in; the other information is common to all menu add-ins.
TIP
When creating a UsysRegInfo table for an Access 2007 add-in that is to be run in Windows Vista, you need to change the capitalization of Menu Add-ins to Menu AddIns (capitalizing the I); if you leave it lowercased as for previous versions of Office and Windows, you will not be able to install the add-in.
TABLE 14.3
HKEY_CURRENT_ACCESS_PROFILE\Menu Add-Ins\ &Back up Database HKEY_CURRENT_ACCESS_PROFILE\Menu Add-Ins\ &Back up Database HKEY_CURRENT_ACCESS_PROFILE\Menu Add-Ins\ &Back up Database
TIP
If you make copies of your add-in library database from time to time while working on it (always a good idea), dont save the copies in the AddIns folder, because otherwise they will show up as extra selections in the Add-In Manager dialog, and it may not be clear which is the latest version of the add-in when installing it, or which version is running when you use the add-in.
478
14
Wizards
My Design Schemes add-in (which finally can be retired because of the superior formatting features of Access 2007) contains several Form Wizard add-ins. Form Wizard add-ins need four rows in the USysRegInfo table, as described in the following list (the syntax is similar for Report wizards): n In the first row of the add-in, the Subkey field (which is the same for all the add-ins rows) has the Registry key information, referencing the Form Wizards section under the HKEY_CURRENT_ACCESS_PROFILE key (which references the currently running version of Access), and ending with the command name to display on the Add-Ins menu, with an ampersand if desired to make a hot key. The Type field has a value of 0, indicating the start of a new add-in. The ValName and Value fields are blank. n In the add-ins second row, the Type field has a value of 1, the ValName field has the value Description, and the Value field has text to display on the initial wizard screen in the case of the Custom Form Wizard, that is the New Form dialog. n In the add-ins third row, the Type field has a value of 1, the ValName field has the value Library, and the Value field has the location and name of the library database, using the |ACCDIR\ placeholder to point to the AddIns folder. n In the add-ins fourth row, the Type field has a value of 1, the ValName field has the value Function, and the Value field has the name of the function to run (without an equals sign). Table 14.4 lists the USysRegInfo rows for the Custom Form Wizard command on the AddIns menu from the Design Schemes add-in.
TABLE 14.4
HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Form Wizards\Custom Form Wizard HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Form Wizards\Custom Form Wizard HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Form Wizards\Custom Form Wizard HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Form Wizards\Custom Form Wizard
0 1 Description Select a form type and color scheme for form background colors and control properties |ACCDIR\Design Schemes.mda
1 1
Library Function
StartDSWizard
479
Part III
NOTE
In a menu add-in, the function called from the add-in is preceded by an equals sign; in a wizard add-in, there is no equals sign.
Property Builders
My LNC Rename add-in (originally created in Access 97 and updated for Access 2000) is still useful in Access 2007 running on Windows XP, because Microsoft has not yet implemented automatic object and control naming according to a naming convention. This add-in lets you automatically rename database objects and form and report controls according to the Leszynski Naming Convention. It includes several menu add-ins and two property builders, which run from the Name property of a control (for renaming a single control) or the Detail section of a form or report (for renaming all controls in a form or report).
CROSS-REF
For more details on the LNC Rename add-in, see my book Expert One-on-One Microsoft Access Application Development (ISBN: 0764559044).
Property builders require five rows in the USysRegInfo table, as described in the following list: n In the first row of the add-in, the Subkey field (which is the same for all the add-ins rows) has the Registry key information, referencing the Property Wizards section under the HKEY_CURRENT_ACCESS_PROFILE key (which references the currently running version of Access), and ending with the command name to display on the Add-Ins menu, with an ampersand if desired to make a hot key. The Type field has a value of 0, indicating the start of a new add-in. The ValName and Value fields are blank. n In the add-ins second row, the Type field has a value of 1, the ValName field has the value Description, and the Value field has text to display on the Builder dialog in the case of the LNCBuilder property wizard, that is the Choose Builder dialog opened from the Name property of a controls properties sheet. n In the add-ins third row, the Type field has a value of 4, the ValName field has the value Can Edit, and the Value field has a value of 1, indicating that the wizard can be called for an existing object. n In the add-ins fourth row, the Type field has a value of 1, the ValName field has the value Library, and the Value field has the location and name of the library database, using the |ACCDIR\ placeholder to point to the AddIns folder. n In the add-ins fifth row, the Type field has a value of 1, the ValName field has the value Function, and the Value field has the name of the function to run (without an equals sign). Table 14.5 lists the USysRegInfo rows for the property wizard for renaming a single control from the LNC Rename add-in.
480
14
TABLE 14.5
HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Property Wizards\Name\LNC Builder HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Property Wizards\Name\LNC Builder HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Property Wizards\Name\LNC Builder HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Property Wizards\Name\LNC Builder HKEY_CURRENT_ACCESS_PROFILE\Wizards\ Property Wizards\Name\LNC Builder
LNCBuilder
NOTE
Strictly speaking, a library database contains add-ins (usually several), but usually the entire library database is referred to as an add-in.
481
Part III
n An add-in can display forms, usually as unbound dialogs. Typically, all add-ins except menu add-ins use one or more forms (and menu add-ins can use forms too). The Extras 2007 add-in has a setup form where users can enter information that will be used by various procedures in the add-in. n You can reference tables in the add-in library database using the CodeDb syntax, which lets you store add-in data in tables if needed. This is useful when you want to store information that will be used for all databases running the add-in, for example the lists of table and query prefixes for excluding tables and queries from listing, in the Extras 2007 add-in. n Replace macros and queries with code run from public functions, so they can be run directly from the librarys module(s). n Bound forms run from an add-in have as their record source tables in the code database, not the calling database. If the form needs to display data from the calling database, it must be copied to a table in the code database after being filled with data from the calling database, as I do in the Extras 2007 add-in for backup options, and table and query fields. n The DDL CreateTable statement creates a table in the current database; if you need to create a table in the code database, you have to use the more complex TableDef method in DAO, specifying CodeDb as the database in which to create the table. n An add-in intended to work in both Access 2007 and earlier versions of Access may need to process both .mdb and .accdb extensions, or deal with the new Attachment data type. n When the CopyObject method is run from an add-in, the code looks for the source object (the SourceObjectName argument) first in the library database, and then in the current database. This may require you to create a copy of a table, say with Blank at the end of its name, in order to copy a fresh, blank table to the calling database, overwriting a filled table, as I do in the Extras 2007 add-in code. Otherwise, you will end up copying the filled table from the calling database back to itself. n When run from a library database, the RunSQL method of the DoCmd object works on tables in the calling database, and the OpenQuery method works on tables in the code database.
482
14
n Turn navigation buttons off. n If your wizard has several forms, make sure that controls used on more than one form in a series of wizard screens appear in the same place on each form. n Make all the wizard forms dialog boxes by setting their Modal property to Yes, PopUp to Yes, and BorderStyle to Dialog, so the user cant move to the next box until the current one has been filled in. n Copy wizard images from the Access wizards, save them as image files, and place them on your wizard forms.
Extras Options
The fdlgExtrasOptions form module contains the code behind the Extras Options dialog, where you can set up your preferences for the backup save folder, or the prefixes to exclude when listing fields:
Option Compare Database Option Explicit Private Private Private Private Private Private Private Private Private Private Private Private Private Private dbsCalling As DAO.Database fd As Office.FileDialog intChoice As Integer prps As DAO.Properties prp As DAO.Property strBackupChoice As Integer strBackupPath As String strCallingDb As String strPropName As String strTable As String tdfs As DAO.TableDefs tdf As DAO.TableDef txt As Access.TextBox varPropValue As Variant
Private Sub cmdCancel_Click() On Error Resume Next DoCmd.Close acForm, Me.Name, acSaveNo End Sub
483
Part III
484
14
On Error Resume Next DoCmd.RunCommand acCmdSizeToFitForm On Error GoTo ErrorHandler intChoice = Nz(Me![BackupChoice], 2) Select Case intChoice Case 1 Me![cmdCustomBackupPath].Enabled = False Case 2 Me![cmdCustomBackupPath].Enabled = False Case 3 Me![cmdCustomBackupPath].Enabled = True End Select ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & err.Number _ & ; Description: & err.Description Resume ErrorHandlerExit End Sub Private Sub fraBackupOptions_AfterUpdate() On Error GoTo ErrorHandler intChoice = Nz(Me![fraBackupOptions].Value, 2) strBackupChoice = CStr(intChoice) strBackupPath = Nz(Me![BackupPath]) Select Case intChoice Case 1 Me![cmdCustomBackupPath].Enabled = False Case 2 Me![cmdCustomBackupPath].Enabled = False Case 3 Me![cmdCustomBackupPath].Enabled = True End Select
485
Part III
basExtras Module
The basExtras standard module contains functions that are called from the USysRegInfo table:
Public Function ExtrasOptions() Called from USysRegInfo (menu add-in) On Error GoTo ErrorHandler Dim Dim Dim Dim Dim strBackEndSyntaxChoice As String strBackEndSyntax As String strBackEndPathChoice As String strBackEndPath As String strDefault As String
Get info from database properties in the calling database, and write them to zstblBackupChoices in the code database for use as forms record source:
Set dbsCalling = CurrentDb strPropName = BackupChoice strDefault = 2 strBackupChoice = GetProperty(strPropName, strDefault) Debug.Print Backup choice: & strBackupChoice strPropName = BackupPath strDefault = strBackupPath = GetProperty(strPropName, strDefault) Debug.Print Backup path: & strBackupPath strTable = zstblBackupChoice Set dbsCode = CodeDb Set rst = dbsCode.OpenRecordset(strTable)
486
14
rst.MoveFirst rst.Edit rst![BackupChoice] = strBackupChoice rst![BackupPath] = strBackupPath rst.Update rst.Close On Error Resume Next
487
Part III
Copy various objects to the calling database, if they dont already exist. These objects are needed to support the add-ins functionality:
Set dbsCalling = CurrentDb strCallingDb = CurrentDb.Name Set tdfsCalling = dbsCalling.TableDefs strTable = zstblAccessDataTypes Set tdfCalling = tdfsCalling(strTable) DoCmd.SetWarnings False If tdfCalling Is Nothing Then Debug.Print strTable & not found; about to copy it DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable End If Set ctr = dbsCalling.Containers(Reports) strReport = zsrptTableAndFieldNames Set doc = ctr.Documents(strReport) If doc Is Nothing Then DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strReport, _ sourceobjectType:=acReport, _ sourceobjectname:=strReport End If strReport = zsrptQueryAndFieldNames Set doc = ctr.Documents(strReport) If doc Is Nothing Then DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strReport, _ sourceobjectType:=acReport, _ sourceobjectname:=strReport End If ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & err.Number _ & ; Description: & err.Description Resume ErrorHandlerExit End Function
488
14
Back up Database
The BackupFrontEnd function is called from the USysRegInfo table to back up the current database to the path selected in the Extra Options dialog:
Public Function BackupFrontEnd() Called from USysRegInfo On Error GoTo ErrorHandler Set dbsCalling = CurrentDb Set tdfsCalling = dbsCalling.TableDefs Set fso = CreateObject(Scripting.FileSystemObject) strCurrentDB = Application.CurrentProject.Name Debug.Print Current db: & strCurrentDB intExtPosition = InStr(strCurrentDB, .) strExtension = Mid(strCurrentDB, intExtPosition) intExtLength = Len(strExtension)
Create the backup path string depending on the users choice, with a default of 2 (Backups folder under the database folder) in case the user has not made a choice:
strPropName = BackupChoice strBackupChoice = GetProperty(strPropName, 2) Debug.Print Backup choice: & strBackupChoice strPropName = BackupPath strPath = GetProperty(strPropName, ) Debug.Print Custom backup path: & strPath Select Case strBackupChoice Case 1
489
Part III
Custom folder
strBackupPath = strPath & \ End Select Debug.Print Backup path: & strBackupPath
Create folder.
Set sfld = fso.CreateFolder(strBackupPath) End If End If
If setup has not been done, copy zstblBackupInfo to the calling database:
strCallingDb = CurrentDb.Name strTable = zstblBackupInfo Set tdfCalling = dbsCalling.TableDefs(strTable) If tdfCalling Is Nothing Then Debug.Print strTable & not found; about to copy it DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable Debug.Print Copied & strTable End If
490
14
strProposedSaveName = strBackupPath & strSaveName Debug.Print Backup save name: & strProposedSaveName strTitle = Database backup strPrompt = Save database to & strProposedSaveName _ & ? strSaveName = Nz(InputBox(prompt:=strPrompt, _ title:=strTitle, Default:=strProposedSaveName))
491
Part III
strFullDBName As String strFileName As String strFullPath() As String strDBName As String intUBound As Integer strConnect As String
Set dbsCalling = CurrentDb Set tdfsCalling = dbsCalling.TableDefs Set fso = CreateObject(Scripting.FileSystemObject) strCurrentDB = Application.CurrentProject.Name Debug.Print Current db: & strCurrentDB strDayPrefix = Format(Date, mm-dd-yyyy) intExtPosition = InStr(strCurrentDB, .) strExtension = Mid(strCurrentDB, intExtPosition) intExtLength = Len(strExtension) strExcludeTable = zstblTablePrefixes
Check whether there are any linked tables, and exit if not.
strBackEndDBNameAndPath = On Error Resume Next
492
14
Extract back end name and path from Connect property string.
strFullPath = Split(strBackEndDBNameAndPath, \, -1, _ vbTextCompare) intUBound = UBound(strFullPath) strBackEndDBName = strFullPath(intUBound) strBackEndDBPath = Mid(strBackEndDBNameAndPath, 1, _ Len(strBackEndDBNameAndPath) - Len(strBackEndDBName)) Debug.Print Database name: & strBackEndDBName Debug.Print Database path: & strBackEndDBPath On Error Resume Next
493
Part III
Custom folder
strBackupPath = strPath End Select Debug.Print Backup path: & strBackupPath On Error Resume Next
Create folder.
Set sfld = fso.CreateFolder(strBackupPath) End If End If On Error GoTo ErrorHandler
494
14
Len(strBackEndDBName) - intExtLength) _ & Copy & BackEndSaveNo _ & , & strDayPrefix & strExtension strProposedSaveName = strBackupPath & strSaveName Debug.Print Backup save name: & strProposedSaveName strTitle = Database backup strPrompt = Save back end database to _ & strProposedSaveName & ? strSaveName = Nz(InputBox(prompt:=strPrompt, _ title:=strTitle, Default:=strProposedSaveName))
495
Part III
Create a new, blank table in the code database to fill with data:
DoCmd.CopyObject destinationdatabase:=strCodeDB, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable & Blank
Fill the table in the code database with table and field names from the calling database:
Set rst = dbsCode.OpenRecordset(strTable, dbOpenTable) strExcludeTable = zstblQueryPrefixes For Each qdf In dbsCalling.QueryDefs strQuery = qdf.Name Debug.Print Query name: & strQuery If ExcludePrefix(strQuery, strExcludeTable) = _ False Then Set flds = qdf.Fields For Each fld In flds strFieldName = fld.Name With rst .AddNew !QueryName = strQuery !FieldName = strFieldName !DataType = fld.Type !Required = fld.Required .Update End With Next fld End If
496
14
Copy the filled table to the calling database so it will be available for printing in the calling database:
strTable = zstblQueryAndFieldNames Set tdfCode = dbsCode.TableDefs(strTable) DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable DoCmd.OpenTable strTable strTitle = Table filled strPrompt = Print report now? intReturn = MsgBox(strPrompt, vbQuestion + vbYesNo, _ strTitle) If intReturn = vbYes Then strReport = zsrptQueryAndFieldNames DoCmd.OpenReport strReport End If ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & err.Number _ & ; Description: & err.Description Resume ErrorHandlerExit End Function
497
Part III
Delete the old table in the calling database (if there is one):
Set tdfsCalling = dbsCalling.TableDefs Set tdfCalling = tdfsCalling(strTable) If Not tdfCalling Is Nothing Then tdfsCalling.Delete (strTable) End If
Create a new, blank table in the code database to fill with data:
DoCmd.CopyObject destinationdatabase:=strCodeDB, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable & Blank
Fill the table in the code database with table and field names from the calling database:
Set rst = dbsCode.OpenRecordset(strTable, dbOpenTable) strExcludeTable = zstblTablePrefixes For Each tdfCalling In dbsCalling.TableDefs strTable = tdfCalling.Name If ExcludePrefix(strTable, strExcludeTable) = _ False Then Set flds = tdfCalling.Fields For Each fld In flds strFieldName = fld.Name With rst .AddNew !TableName = strTable !FieldName = strFieldName !DataType = fld.Type !ValidationRule = fld.ValidationRule !Required = fld.Required .Update End With Next fld End If Next tdfCalling rst.Close
498
14
Copy the filled table to the calling database so it will be available for printing in the calling database:
strTable = zstblTableAndFieldNames Set tdfCode = dbsCode.TableDefs(strTable) DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable DoCmd.OpenTable strTable strTitle = Table filled strPrompt = Print report now? intReturn = MsgBox(strPrompt, vbQuestion + vbYesNo, _ strTitle) If intReturn = vbYes Then strReport = zsrptTableAndFieldNames DoCmd.OpenReport strReport End If ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & err.Number _ & ; Description: & err.Description Resume ErrorHandlerExit End Function
Other Procedures
The SetProperty and GetProperty functions are called from various procedures in the addin to save values to custom database properties, or retrieve values from them:
Public Sub SetProperty(strName As String, lngType As Long, _ varValue As Variant) Called from various procedures On Error GoTo ErrorHandler
499
Part III
500
14
The SaveNo and BackEndSaveNo functions create an incremented number for the database (or back-end database) copies:
Public Function SaveNo() As String Called from BackupFrontEnd() On Error GoTo ErrorHandler
501
Part III
Once opened, the properties sheet looks like the one in Access 2003 (shown in Figure 14.9). The text you enter in the library databases properties sheet is used as follows: n The Title fields value is displayed as the add-ins name in the list of available add-ins. n The value of the Company field (not the Author field, as you might expect) appears as the add-in authors name under the list of available add-ins. n The Comments fields value appears as the add-ins description at the bottom of the AddIn Manager dialog.
502
14
FIGURE 14.9 Entering the add-ins identification information in the properties sheet.
Troubleshooting Add-ins
If you need to step through add-in code to determine what is causing a problem, you have two options. One is to place a Stop statement in the add-in code, which will stop the code at that point when it is executing, so you can step through the code from that point. To do this, you must first close any open database, then open the add-in library database and add the Stop statement, save and close the add-in, then open a database and run the add-in that has the Stop statement in its code. Later, you will need to remove the Stop statement from the add-in code in a similar fashion. The other (and quicker) alternative is to set a reference to the library database, so you can open its code modules and place breakpoints, and even modify the code temporarily, to test various alternatives. To set a reference to an Access add-in library database, complete the following steps:
TIP
If you plan to set a reference to a library database so you can step through its code easily (as described next), give its Visual Basic project a meaningful name, so it will say Extras (or whatever) instead of Project1 in the References dialog. To name the VB project, open the Visual Basic window, select the project row (the top row) in the Project Explorer, and rename it in the Name property of the properties sheet, as shown in Figure 14.10.
503
Part III
1. Open a module in any Access database, and select Tools, References to open the References dialog, as shown in Figure 14.11.
504
14
2. Click the Browse button to browse for the add-in library database, and select Add-ins (*.mda) in the Files of Type drop-down list, if you are setting a reference to an Access 2003 or earlier (.mda) library database, or All Files (*.*) to set a reference to an Access 2007 (.accda) library database (see Figure 14.12).
3. Click Open to set the reference; the project name of the add-in now appears checked in the References dialog, as shown in Figure 14.13.
505
Part III
4. Now you can see the add-in project in the Project Explorer, and open its module(s) and work with them much like modules in the current database, as shown in Figure 14.14.
FIGURE 14.14 Opening an add-in module in the Visual Basic window of a database.
CAUTION
Though you can edit code in a library database after setting a reference to it, and run the code to test whether the modifications fix a problem, the changes arent saved to the library database, so save any modified code to a text file, which you can then copy and paste into the library database when you next open it directly.
506
14
Sometimes this error occurs when you have made changes to the add-in, but Access is still running the old code. In case this is the cause of the problem, try uninstalling the add-in from the Add-In Manager dialog, then reinstall it. In some cases, this will fix the problem. If you still get the error message after reinstalling the add-in, it may be because you created a sub instead of a function for use in the USysRegInfo table (only functions can be called from the Registry), or you changed the name of the function to be called, so the one in the Registry cant be found. Check that the function entries in the USysRegInfo table match the function names in the library database (and that the called procedures are functions, not subs). If a form in your add-in doesnt appear when the function that should open it is run, without any error message, this may be because the forms record source is missing. You wont get an error; the form just doesnt open. Check that the forms record source exists, and is located in the code database, not the calling database. In some cases it may be necessary to fill a table in the calling database, and then copy it back to the code database to use as a forms record source, as I do in the Extras add-in. If you get a This feature is not installed error message when trying to run the add-in, it might result from any number of errors in the add-ins code. First, check for problems in the following areas: n Incorrect syntax in an add-in function (for example, the wrong number or type of arguments). n Mismatch between the function name in the USysRegInfo table and the code module. n The add-ins name was changed, but the reference to it in the USysRegInfo table still has the old name. n General syntax errors in the add-in code. n The add-in code has not been compiled. Then, complete the following steps:
1. First uninstall the database and close Access. 2. Open the library database, fix any errors, and compile the add-in database. 3. Repeat as needed until the add-in runs without errors.
Installing an Add-in
An add-in only needs to be installed once in any Access database; after it is installed, it is available to all Access databases. To install the Extras 2007 add-in, first copy the library database to your AddIns folder. In Access 2007 running on Windows XP, the AddIns folder is in the same place as in Office 2003, C:\Documents and Settings\User Name\Application Data\Microsoft\AddIns; if you are running Windows Vista, the Addins folder is in J:\Users\Helen Feddema\AppData\Roaming\ Microsoft\AddIns.
507
Part III
If you are attempting to install an add-in in Access 2007 running on Windows Vista, you may get a security warning. In that case, try running Access as an administrator, by right-clicking the MSACCESS.EXE file in the Office 12 subfolder under the Microsoft Office folder, and selecting the Run as administrator selection. Now, you should be able to install the add-in. Another Vista requirement: In the USysRegInfo table, change the capitalization of Menu Add-ins (to Menu Add-Ins, with a capital I). According to Microsoft, this case sensitivity will be addressed in an upcoming service patch, but you need to make the change to enable menu add-ins to work in Vista right now. If you prefer to keep your own add-ins in a custom folder, you can add another folder to the list of trusted locations by selecting File, Access Options, and then selecting the Trust Center page of the Access Options dialog, as shown in Figure 14.16.
CROSS-REF
FIGURE 14.16
See the Getting Your Add-ins to Work in Vista sidebar in Chapter 15 for more information about the specific requirements for installing add-ins in Windows Vista.
Click the Trust Center Settings button and select the Trusted Locations page. Next, use the Add new location button to add the folder where you want to store your add-ins, as shown in Figure 14.17.
508
14
Next, open any Access database, and select the Database Tools tab of the Ribbon, click the Add-Ins drop-down in the Database Tools group, and select Add-In Manager (see Figure 14.18).
Once you have opened the Add-In Manager, it looks just like the familiar dialog from earlier versions of Access, listing the add-ins that are found in the AddIns folder, and letting you install or uninstall them. To install the Extras 2007 add-in, select it and click the Install button, as shown in Figure 14.19.
509
Part III
After clicking Install, an x appears to the left of the installed add-in, and when you close the AddIn Manager, several new selections appear on the Add-Ins menu, as shown in Figure 14.20.
FIGURE 14.20 Add-ins from the Extras 2007.accda library database on the Add-Ins menu.
NOTE
The three menu add-ins starting with Rename at the bottom of the list are from my LNC Rename add-in.
TIP
If you are creating an add-in that can be used in several versions of Access (it doesnt need any special new features of Access 2007, and doesnt create menus or toolbars that will only work in older versions), create the add-in in an older format, so it can run in both the older version(s) and Access 2007. The LNC Rename.mda add-in will run in any version of Access from Access 2000 through Access 2007, at least if you are running on Windows XP.
510
14
ou may be used to signing code in Access 2003 (or earlier) databases with a digital signature (see the "Creating a Digital Signature for Signing Your Access VBA Code" Sidebar in Chapter 12 for details on how to create your personal digital signature). It might seem like a good idea to sign an Access 2007 add-in's code with a digital signaturebut if you try it, you will run into roadblocks. After creating a personal digital signature, if you try to select the signature from Tools, Digital Signature in the add-in's module window, you will get a lengthy and puzzling message (see the figure below). Note that the .accda format is not mentioned.
You can follow the instructions for an .accdb database, and select File, Publish, Package and Sign from the library database, select the digital signature, and create a signed package (it will have the .accdc extension). But if you attempt to install the .accdc file as an add-in, you will get an "Unrecognized database format" error message, and you can't install the signed package file. Thus (at the present time, at least) there is no way to digitally sign an Access 2007 add-in library database.
Extras Options
This selection opens a setup dialog (shown in Figure 14.21) with an option group for selecting the folder for storing database backups, and two subforms listing the table and query prefixes for tables and queries you would like to omit when using the List Query Fields or List Table Fields add-ins. These tables are pre-filled with system prefixes, dashes and underscores, and Copy Of (the latter is the prefix used in Access 2007 object copies). If you select the Custom Path option, a command button is enabled that opens a Folder Picker dialog where you can select a custom backup folder. The backup choice is stored separately for each database, using database properties; the prefixes, however, are stored in the code database, so they are the same for all databases. You dont need to open the Extras setup dialog to use the other menu add-ins; if you havent made any selections, the code uses the default backup choice of option 2 (Backups folder under database folder), and the standard prefixes.
511
Part III
Back up Database
This option makes a backup of the current database (either a standalone database or a front-end database), presenting the proposed name of the database copy in an InputBox, so you can edit it if desired, for example to add specific information about a milestone achieved. The InputBox is shown in Figure 14.22.
FIGURE 14.22 A proposed name for a backup copy of the Northwind sample database.
512
14
NOTE
FIGURE 14.23
Only select queries will be listed. Although queries of other types (action queries) are listed by name in the QueryDefs collection, they have no fields to list.
Note the Expr1008 field name, indicating that the same field is included in the query twice. The fields in the table are listed in the order that they occur in the query in design view; the report based on this table gives an alphabetical listing, as shown in Figure 14.24. The table and report are stored in the calling database, and thus they can be opened later without re-running the menu add-in.
513
Part III
Summary
This chapter dealt with creating Access add-ins in the Access 2007 (.accda) library database format (you can use the same techniques to create add-ins in the older .mda format for use with databases created in previous Access database formats as well as Access 2007). Access add-ins let you encapsulate a set of database objects (primarily code and forms), for use in any Access database, as a way of adding extra functionality to a database without the need to manually import objects into any database where you need the functionality. The next chapter covers using Ribbon XML to work with the Access ribbon, in Access add-ins as well as other types of add-ins.
514
IN THIS CHAPTER
Customizing the Ribbon in an Access database Customizing the Ribbon with an Access add-in Tools for working with XML code
515
Part III
he MSDN document Customizing the Office (2007) Ribbon User Interface for Developers (Parts 1 through 3) is a very useful reference when creating Ribbon XML for Access (and other Office applications).
The list of Control IDs (Access Ribbon Controls.xls) is invaluable for working with the Ribbon; it lists the control, group, and tab names you need to use when creating Ribbon XML code. You can download this worksheet from http://www.microsoft.com/downloads/details.aspx?family id=4329d9e9-4d11-46a5-898d-23e4f331e9ae&displaylang=en on the Microsoft web site. Several blogs are also valuable resources for information on working with the Ribbon in VBA or VB code: In Erik Ruckers blog, see the July 13, 2006 posting on Customizing the New Access UI for information on customizing the Access Ribbon. The blogs maintained by Jensen Harris and Patrick Schmid have lots of valuable information on Ribbon customization (Jensen Harris is the program manager in charge of the Office UI team, and Patrick Schmid is an MVP). The Third of Five blog is also useful. See the Office Developer Center web site for the latest list of Office-related blogs. However, note that this worksheet was last updated in November 2006, and some of the names have changed since then, so it is not entirely accurate, especially for group and tab names. Here are links to the resources mentioned in the previous paragraph:
n Customizing the Office (2007) Ribbon User Interface for Developers, Parts 1 through 3: n http://msdn2.microsoft.com/en-us/library/ms406046.
aspx#OfficeCustomizingRibbonUIforDevelopers_AppLevel
n Erik Ruckers blog: http://blogs.msdn.com/access n Jensen Harris blog: http://blogs.msdn.com/jensenh/default.aspx n Patrick Schmids blog: http://pschmid.net/blog/2006/10/09/58 n Third of Five Blog: http://blogs.msdn.com/thirdoffive/
516
15
Figure 15.1 shows the TreeView tab of the XML Notepad editor, with the List Fields XML code. This view shows each attribute on a separate line of the tree in the left pane, with its value displayed in the right pane.
CAUTION
The XML Notepad 2007 utility was updated towards the end of the beta of Office 2007/Windows Vista, and it worked well at that time. Unfortunately, in the release version of Vista, it has a curious problem: it will only run minimized or maximized, not restored. Hopefully this glitch will clear up with a patch some time soon. Until that time, when you run this utility, it will appear minimized; right-click its taskbar icon and select Maximize to open it full-screen.
FIGURE 15.1 The Tree View tab of the XML Notepad 2007 editor.
517
Part III
The XSL Output tab of this editor shows the code in an indented layout, as shown in Figure 15.2.
FIGURE 15.2 The XSL Output tab of the XML Notepad 2007 editor.
518
15
If you have Visual Studio 2005 (any edition), use its built-in XML editor; otherwise, I recommend the XML Notepad 2007 editor for working with XML code.
519
Part III
he best discussion of Access 2007 Ribbon customizability (or the lack thereof) is from the October 18, 2006 entry on MVP Patrick Schmids blog:
If you ask me about the customizability of the new Ribbon UI in Office 2007, my answer would be: too little, too difficult. Compared to previous Office versions, especially Office 2003, 2007 simply has a serious customization deficiency. In fact, most users will probably conclude that the Ribbon cannot be customized at all. In contrast, Office 2003 is the most customizable Office ever. You can locate your menus and toolbars anywhere you want on the screen, create your own menus and toolbars, change icons and labels, modify toolbars and menus, and so on. There is almost no limit as to what components of the UI a user can alter. Customizing Office 2003 is also easy to do, as alteration can be achieved with a few mouse clicks. The Ribbon UI of Office 2007 though is a completely different story. Static with very limited customizability is probably the description most users would give this new UI. Most users probably only discover the Quick Access Toolbar (QAT) and then conclude that this must be it. Is that really all there is? How did we end up with such a lack of customization?
520
15
Although you cant customize the standard Ribbon tabs and groups, you can add new tabs and groups to the Ribbon, though the technique is completely different than writing VBA code to work with CommandBars, as in past versions of Access. There are several steps to customizing the Ribbon in an Access database:
1. Write XML code to define the Ribbon customizations. 2. Create a table in the Access database to store the Ribbon names and their XML code. 3. (Optional) Write VBA callback procedures to run from custom Ribbon command buttons. 4. Close the database, then reopen it, to load the Ribbon(s) from the table. 5. Select the Ribbon to load into the database. 6. Close and reopen the database to load the Ribbon.
In addition to creating the XML code, VBA code (if needed), and table, you also have to close and reopen the database twice to get the Ribbon customizations to appear once to load the Ribbon, and again after selecting the Ribbon to use in the database. The next few sections guide you through customizing the Ribbon in an Access database.
NOTE
521
Part III
TABLE 15.1
customUI ribbon
The top-level element for a custom Ribbon The Ribbon definition Set the startFromScratch attribute to true to create a new, blank Ribbon. (The quotes are needed, a difference from VBA code with its True and False keywords.) If set to false or omitted, the customizations are applied to the standard Ribbon.
Creates or references a Ribbon tab Creates or references a group on a tab Unique name of a custom control Name of a standard control Text displayed on a control A command button on a Ribbon The onAction attribute specifies the name of a function to run when the button is clicked. Automatically limited to list selections. Users can enter selections or select from the list. Set with the name of a built-in Ribbon control (or a custom image you have created). The choices are normal and large.
A drop-down list A drop-down list that allows manual entries The image to use for the control
The size of the control The text to display in the pop-up that appears when your mouse hovers over a control Whether the control is visible or not Whether the control is enabled or not
The choices are true or false. The choices are true or false.
NOTE
522
Element names in XML use camel-casing notation; the first letter of the element name is lowercase, and other components have their first letter capitalized (giving the appearance of a camels hump). Example: dropDown.
15
You can start by creating a table to hold the Ribbon XML code, or create the XML code first, as you prefer. If you have just one table of Ribbon code, the convention is to call it USysRibbons, with the USys prefix indicating that it is a user-created system table. The table has three fields, as described in Table 15.2.
TABLE 15.2
ID RibbonName RibbonXML
Unique ID field Name of the custom Ribbon The XML code with Ribbon settings
he USysRibbons table is a system table, so you wont see it unless you turn on display of system tables by opening the Access Options screen from the Office menu:
523
Part III
continued And then select the Current Database page and click the Navigation Options button:
524
15
The following screen shot shows the USysRibbons table in datasheet view, with the record for the ListFields Ribbon visible:
CROSS-REF
Although you can edit the XML code directly in the Access USysRibbons table (use Ctrl+Enter to go to a new line), its a lot easier working with it in an XML editor, such as the one in VB 2005 (standard or Express), or the XML Notepad 2007 utility. Figure 15.4 shows what the same code looks like in the XML Notepad 2007 editor, after opening the code saved as Ribbon.xml.
NOTE
IntelliSense doesnt work for Access Ribbon XML code in Visual Basic 2005 Express, but it does work in Visual Studio 2005.
It is much easier to work with XML code in the XML Notepad 2007 or the Visual Studio 2005 editor. In the XML Notepad 2007 editor, you have a TreeView pane on the left, and each attribute is shown with its matching value in the right pane; in the Visual Studio 2005 editor, the components are color-coded and you have IntelliSense to aid in creating code. Bracket matching (see the shaded <tabs> and </tabs> names in the figure) also helps, in case you started a bracketed code segment and forgot to end it. For XML code in the Visual Studio 2005 editor, the colors have the meanings listed in Table 15.3.
525
Part III
TABLE 15.3
Red Blue
Attribute Name Attribute Value Delimiter Keyword Comment Name Doc Tag Processing Instruction Text XSLT Keyword
526
15
NOTE
In order for XML documents to be able to use elements and attributes that have the same name but come from different sources, there must be a way to differentiate between different sources of markup elements. In XML, a namespace is a collection of names, identified by a URL reference, that are used as element types and attribute names in XML documents.
The XML code starts with specifying the namespace as http://schemas.microsoft.com/ office/2006/01/customui. This is the only XML namespace used for Office 2007 Ribbons. Next, the Ribbons startFromScratch attribute is set to false (for VBA programmers, note that in XML code, the syntax is false, with the quotes, not just False, as in VBA code). This setting means that you are modifying the standard toolbar, as opposed to creating a Ribbon from scratch. The next sections show how to customize the Ribbon in various ways.
CROSS-REF
See Table 15.1 for a list of the most commonly used elements for creating tabs, groups, and controls on the Ribbon.
Controls have (minimally) id and label attributes, and also an enabled attribute (usually set to true). They typically display an image, which is usually set with imageMso argument, using a value corresponding to the standard Ribbon button that has the image you want to display.
NOTE
You cant remove a control from a standard Ribbon group, or add a control to a standard group; see the sidebar earlier in this chapter for Patrick Schmids illuminating commentary on this issue. For Microsofts justification of this policy, see the Developer Overview of the User Interface for the 2007 Microsoft Office System article (http://msdn2.microsoft.com/ en-us/library/aa338198.aspx). It boils down to something like We made the Ribbon perfect, so users dont need to customize it. Needless to say, I (and many other developers and power users) disagree. The Ribbon is indeed a great improvement over command bars and menus, but I would really like to be able to drag a button I need to a standard group, or remove one that I never use.
To use the Form image on a custom control, use CreateForm as the imageMso argument value when defining the button in XML code. The controls size attribute has only two selections: large and normal. Lastly, for a command button, set the onAction attribute with the name of a function to run when the button is clicked.
527
Part III
f you want to assign a familiar Access image to a button on the Ribbon, you need to know the name of the standard Access control that uses this image, so you can set the imageMso argument for the button with that name. You can download an Excel worksheet (AccessRibbonControls.xls) with this information from http://www.microsoft.com/downloads/details.aspx?familyid= 4329d9e9-4d11-46a5-898d-23e4f331e9ae&displaylang=en on the Microsoft web site.
CAUTION
Even though Office 2007 has been released, the latest worksheets of control names were prepared during the beta (November 2006), and they are not entirely accurate for the release version of Access, especially for tab names. Hopefully an updated set of worksheets will be posted soon.
However, there is another, more convenient method that works fine for many controls: Open the Access Options screen from the Office menu, and click the Customization page. This page is intended for customizing the Quick Access Toolbar, but it is also very useful for finding out control names for use in XML code. To find a control name, first select the Ribbon tab from the Choose command from drop-down list, then select the control in the list. As you hover the mouse over the control, its name appears in parentheses after the friendly name:
Getting the control name for use in assigning an image to a control on the Ribbon.
528
15
To remove a built-in tab or group from the Ribbon, you need to know its name. The AccessRibbonControls.xls worksheet is useful for finding the names of tabs or groups: just sort the worksheet by Control Type, and look at the tab or group rows. To save time, the names of the standard tabs are listed in Table 15.4.
TABLE 15.4
TABLE 15.5
continued
529
Part III
TABLE 15.5
(continued)
GroupCollectData GroupControlAlignment GroupControlAlignmentLayout GroupControlPositionLayout GroupControlsAccess GroupControlSize GroupCreateForms GroupCreateOther GroupCreateReports GroupCreateTables GroupDatabaseSourceControl GroupDatabaseTools GroupDatasheetRelationships GroupDataTypeAndFormatting GroupDesignGridlines GroupExport GroupFieldsAndColumns GroupFieldsTools GroupFindAccess GroupFontAccess GroupFormatting GroupFormattingControls GroupFormattingGridlines GroupGroupingAndTotals GroupImport GroupLayoutShowHide
GroupMacro GroupMacroClose GroupMacroRows GroupMacroShowHide GroupMacroTools GroupMarginsAndPadding GroupMarginsAndPaddingControlLayout GroupMoveData GroupPageLayoutAccess GroupPivotChartActiveFieldAccess GroupPivotChartDataAccess GroupPivotChartFilterAndSort GroupPivotChartShowHide GroupPivotChartTools GroupPivotTableActiveFieldAccess GroupPivotTableDataAccess GroupPivotTableFilterAndSort GroupPivotTableSelections GroupPivotTableShowHideAccess GroupPivotTableToolsAccess GroupPosition GroupPositionLayout GroupPrintPreviewClosePreview GroupPrintPreviewData GroupPrintPreviewPrintAccess
530
15
TABLE 15.6
continued
531
Part III
TABLE 15.6
AutoSumMin Bold Bullets
(continued)
ControlSpecialEffectMenu ControlSpecialEffectRaised ControlSpecialEffectShadowed ControlSpecialEffectSunken ControlSubFormReport ControlTabControl ControlTitle ControlToggleButton ControlUnboundObjectFrame ControlWizards ConvertDatabaseFormat Copy CreateClassModule CreateDiagram CreateEmail CreateForm CreateFormBlankForm CreateFormInDesignView CreateFormMoreFormsGallery CreateFormPivotChart CreateFormSplitForm CreateFormWithMultipleItems CreateLabels CreateMacro CreateModule CreateOtherObjectsMenu CreateQueryFromWizard CreateQueryInDesignView CreateReport CreateReportBlankReport CreateReportFromWizard CreateReportInDesignView CreateShortcutMenuFromMacro
BusinessFormWizard CacheListData ClearGrid ClearMenuAccess CloseDocument ColumnWidth ComAddInsDialog ControlActiveX ControlAlignToGrid ControlAttachment ControlBoundObjectFrame ControlChart ControlImage ControlLayoutRemove ControlLayoutStacked ControlLayoutTabular ControlLine ControlLineColorPicker ControlLineThicknessGallery ControlLineTypeGallery ControlLogo ControlMarginsGallery ControlPaddingGallery ControlPage ControlRectangle ControlSetControlDefaults ControlSnapToGrid ControlSpecialEffectChiseled ControlSpecialEffectEtched ControlSpecialEffectFlat
532
15
CreateStoredProcedure
Delete
CreateTable CreateTableInDesignView CreateTableTemplatesGallery CreateTableUsingSharePointListsGallery CustomizeHide Cut DatabaseAccessBackEnd DatabaseAnalyzePerformance DatabaseAnalyzeTable DatabaseCopyDatabaseFile DatabaseDocumenter DatabaseEncodeDecode DatabaseLinedTableManager DatabaseMakeMdeFile DatabaseMoveToSharePoint DatabaseObjectDependencies DatabasePartialReplica DatabasePermissions DatabasePermissionsMenu DatabaseRelationships DatabaseSetLogonSecurity DatabaseSqlServer DatabaseSwitchboardManager DatabaseUserAndGroupAccounts DatabaseUserLevelSecurityWizard DataRefreshAll DatasheetColumnDelete DatasheetColumnLookup DatasheetColumnRename DatasheetNewField DateAndTimeInsert DefaultView
DeleteTab DrillInto DrillOut ExportAccess ExportDBase ExportExcel ExportHtmlDocument ExportLotus ExportMoreMenu ExportOdbcDatabase ExportParadox ExportSavedExports ExportSharePointList ExportSnapshot ExportTextFile ExportWord ExportXmlFile FieldList FileBackupDatabase FileBackUpSqlDatabase FileCloseDatabase FileCompactAndRepairDatabase FileDatabaseProperties FileDropSqlDatabase FileManageMenu FileNewDatabase FileOpenDatabase FilePackageAndSign FilePrintMenu FilePrintPreview FilePrintQuick FilePublishToSharePoint
continued
533
Part III
TABLE 15.6
FileSave FileSaveAs
(continued)
FindDialog FindNext First10RecordsPreview Font FontAlternateFillBackColorPicker FontColorPicker FontConditionalFormatting FontFillBackColorPicker FontSize FormatCellsDialog FormatPainter FormattingDataType FormattingDecreaseDecimals FormattingFormat FormattingIncreaseDecimals FormattingRequiredField FormattingUnique FormControlButton FormControlCheckBox FormControlComboBox FormControlEditBox FormControlGroupBox FormControlLabel FormControlListBox FormControlRadioButton FormHeaderOrFooterShowHide GoToMenuAccess GoToNewRecord GridlinesColorPicker GridlinesGallery GridlinesStyleGallery GridlinesWidthGallery GridShowHide
FileSaveAsAccess2000 FileSaveAsAccess2002_2003 FileSaveAsAccess2007 FileSaveAsMenuAccess FileSaveAsPdfOrXps FileSendAsAttachment FileServerLinkTables FileServerMenu FileServerTransferDatabase FilterAdvancedByForm FilterAdvancedMenu FilterAfterSelection FilterBeforeSelection FilterBeginsWithSelection FilterBetween FilterBySelection FilterClearAllFilters FilterContainsSelection FilterDoesNotBeginsWithSelection FilterDoesNotContainSelection FilterDoesNotEndWithSelection FilterEndsWithSelection FilterEqualsSelection FilterExcludingSelection FilterIsNotSelected FilterIsSelected FilterLargerThanSelection FilterNotEqualsSelection FilterSmallerThanSelection FiltersMenu FilterToggleFilter
534
15
GroupAddInsCustomToolbars
GroupFormattingControls
GroupAddInsMenuCommands GroupAddInsToolbarCommands GroupAdminister GroupAdpDiagramLayout GroupAdpDiagramShowHide GroupAdpOutputOperations GroupAdpQueryTools GroupAdpQueryType GroupAdpSqlStatementDesignTools GroupAnalyze GroupAutoFormatAccess GroupClipboard GroupCollectData GroupControlAlignment GroupControlAlignmentLayout GroupControlPositionLayout GroupControlsAccess GroupControlSize GroupCreateForms GroupCreateOther GroupCreateReports GroupCreateTables GroupDatabaseSourceControl GroupDatabaseTools GroupDatasheetRelationships GroupDataTypeAndFormatting GroupDesignGridlines GroupExport GroupFieldsAndColumns GroupFieldsTools GroupFontAccess GroupFormatting
GroupFormattingGridlines GroupGroupingAndTotals GroupImport Grouping GroupLayoutShowHide GroupMacro GroupMacroClose GroupMacroRows GroupMacroShowHide GroupMacroTools GroupMarginsAndPadding GroupMarginsAndPaddingControlLayout GroupMoveData GroupPageLayoutAccess GroupPivotChartActiveFieldAccess GroupPivotChartDataAccess GroupPivotChartFilterAndSort GroupPivotChartShowHide GroupPivotChartTools GroupPivotChartType GroupPivotTableActiveFieldAccess GroupPivotTableDataAccess GroupPivotTableFilterAndSort GroupPivotTableSelections GroupPivotTableShowHideAccess GroupPivotTableToolsAccess GroupPosition GroupPositionLayout GroupPrintPreviewClosePreview GroupPrintPreviewData GroupPrintPreviewPrintAccess GroupQueryClose
continued
535
Part III
ImportLotus ImportMoreMenu ImportOdbcDatabase ImportOutlook ImportParadox ImportSavedImports ImportSharePointList ImportTextFile ImportXmlFile IndentDecrease IndentIncrease Italic LabelFontDialog LoadFromQuery MacroArguments MacroConditions MacroConvertMacrosToVisualBasic MacroNames MacroRun MacroShowAllActions MacroSingleStep MailMergeGoToFirstRecord MailMergeGoToNextRecord MailMergeGoToPreviousRecord MailMergeGotToLastRecord ManageReplies MasterViewClose MenuPublish MergeToWord Numbering ObjectBringToFront ObjectGallery ObjectsAlignBottom
536
15
ObjectsAlignLeft
PivotChartMultipleUnified
ObjectsAlignRight ObjectsAlignTop ObjectSendToBack ObjectsGroup ObjectsSelect ObjectsUngroup OleDdeLinks PageBreakInsertOrRemove PageHeaderOrFooterShowHide PageMarginsGallery PageOrientationLandscape PageOrientationPortrait PageSetupDialog PageSizeGallery Paste PasteAppend PasteDuplicate PasteSpecial PasteSpecialDialog PivotAutoCalcAverage PivotAutoCalcCount PivotAutoCalcMax PivotAutoCalcMenu PivotAutoCalcMin PivotAutoCalcStandardDeviation PivotAutoCalcStandardDeviationPopulation PivotAutoCalcSum PivotAutoCalcVariance PivotAutoCalcVariancePopulation PivotAutoFilter PivotChartLegendShowHide PivotChartMultiplePlots
PivotChartSortByTotalAscending PivotChartSortByTotalDescending PivotChartSortByTotalMenu PivotChartType PivotClearCustomOrdering PivotCollapseFieldAccess PivotCollapseFieldAccess PivotCreateCalculatedTotal PivotCreateCalulatedField PivotDropAreas PivotExpandField PivotExpandIndicators PivotExportToExcel PivotFieldList PivotFilterBySelection PivotFormulasMenu PivotGroupItems PivotHideDetails PivotMoveField PivotMoveToColumnArea PivotMoveToDetailArea PivotMoveToFieldArea PivotMoveToFilterArea PivotRefresh PivotRemoveField PivotShowAll PivotShowAsMenu PivotShowAsNormal PivotShowAsPercentOfColumnTotal PivotShowAsPercentOfGrandTotal PivotShowAsPercentOfParentColumnItem PivotShowAsPercentOfParentRowItem
continued
537
Part III
TABLE 15.6
PivotShowDetails
(continued)
PivotShowAsPercentOfRowTotal
QueryCrosstab QueryDataDefinition QueryDelete QueryInsertColumn QueryInsertColumns QueryMakeTable QueryParameters QueryReturnGallery QueryRunQuery QuerySelectQueryType QueryShowTable QuerySqlPassThroughQuery QueryTableNamesShowHide QueryTotalsShowHide QueryUnionQuery QueryUpdate QuickAccessToolbarCustomization RecordsAddFromOutlook RecordsCollapseAllSubdatasheets RecordsDeleteColumn RecordsDeleteRecord RecordsExpandAllSubdatasheets RecordsFreezeColumns RecordsHideColumns RecordsInsertSubdatasheet RecordsMoreRecordsMenu RecordsRefreshMenu RecordsRefreshRecords RecordsRemoveSubdatasheet RecordsSaveAsOutlookContact RecordsSaveRecord RecordsSubdatasheetMenu RecordsTotals
PivotShowOnlyTheBottomMenu PivotShowOnlyTheTopMenu PivotShowTopAndBottomItemsMenu PivotSubtotal PivotSwitchRowColumn PivotUngroupItems PositionAnchoringGallery PositionFitToWindow PostcardWizard PrintColumns PrintDataOnly PrintDialogAccess PrintPreviewClose PrintPreviewEightPages PrintPreviewFourPages PrintPreviewMultiplePagesMenu PrintPreviewTwelvePages PrintPreviewZoom10 PrintPreviewZoom1000 PrintPreviewZoom150 PrintPreviewZoom200 PrintPreviewZoom25 PrintPreviewZoom50 PrintPreviewZoom500 PrintPreviewZoom75 PrintPreviewZoomMenu PrintPreviewZoomTwoPages PropertySheet PublishToPdfOrEdoc QueryAppend QueryBuilder
538
15
RecordsUnfreeze
SharePointListsWorkOffline
RecordsUnhideColumns Redo RelationshipDesignAllRelationships RelationshipsClearLayout RelationshipsDirectRelationships RelationshipsEditRelationships RelationshipsHideTable RelationshipsReport ReplaceDialog ReplicationCreateReplica ReplicationOptionsMenu ReplicationRecoverDesignMaster ReplicationResolveConflicts ReplicationSynchronizeNow Revert RowHeight RulerShowHide SaveAsQuery SaveObjectAs SelectAllAccess SelectAllRecords SelectMenuAccess SelectRecord ServerConnection ServerFilterApply ServerFilterByForm ServerProperties ServerRestoreSqlDatabase SetDatabasePassword SharePointListsDiscardAllChanges SharePointListsDiscardAllChangesAndRefresh SharePointListsDiscardChangesMenu
ShowClipboard ShowMargins SizeToFit SizeToFitAccess SizeToGridAccess SizeToNarrowest SizeToShortest SizeToTallest SizeToWidest SortAndFilterAdvanced SortDown SortRemoveAllSorts SortSelectionMenu SortUp SourceControlAddDatabase SourceControlAddObjects SourceControlCheckIn SourceControlCheckOut SourceControlCreateDatabaseFromProject SourceControlGetLatestVersion SourceControlOptions SourceControlProperties SourceControlRefreshStatus SourceControlRun SourceControlShareObjects SourceControlShowDifferences SourceControlShowHistory SourceControlUndoCheckOut SpellingAccess SubformInNewWindow SubformMenu SynchronizeData
continued
539
Part III
TABLE 15.6
TabAddIns
(continued)
TabReportToolsFormatting TabReportToolsLayout TabReportToolsPageSetupDesign TabReportToolsPageSetupLayout TabSetAdpDiagram TabSetAdpFunctionAndViewTools TabSetAdpSqlStatement TabSetAdpStoredProcedure TabSetFormReportExtensibility TabSetFormTools TabSetFormToolsLayout TabSetMacroTools TabSetPivotChartAccess TabSetPivotTableAccess TabSetQueryTools TabSetRelationshipTools TabSetReportTools TabSetReportToolsLayout TabSetTableToolsDatasheet TabSetTableToolsDesign TabSourceControl TabTableToolsDatasheet TabTableToolsDesignAccess TextDirectionLeftToRight TextDirectionRightToLeft TextHighlightColorPicker TotalsCountRecords TotalsMenu TotalsStandardDeviation TotalsSum TotalsVariance Underline Undo
TabAdpDiagramDesign TabAdpFunctionAndViewToolsDesign TabAdpSqlStatementDesign TabAdpStoredProcedureToolsDesign TabControlLayout TabCreate TabDatabaseTools TabExternalData TabFormToolsDesign TabFormToolsFormatting TabFormToolsLayout TabHomeAccess TableColumnsDelete TableDesign TableIndexes TableListAlertMe TableListPermissions TableRowsDelete TableRowsInsertWord TableSharePointListsModifyColumnsAndSettings TableSharePointListsModifyWorkflow TableSharePointListsRefreshList TableTestValidationRules TabMacroToolsDesign TabOrder TabPivotChartDesign TabPivotTableDesign TabPrintPreviewAccess TabQueryToolsDesign TabRelationshipToolsDesign TabReportToolsAlignment TabReportToolsDesign
540
15
VerticalSpacingDecrease
ViewVisualBasicCodeAccess
VerticalSpacingIncrease VerticalSpacingMakeEqual ViewMessageBar ViewsAdpDiagramPrintPreview ViewsAdpDiagramSqlView ViewsDatasheetView ViewsDesignView ViewsFormView ViewsLayoutView ViewsModeMenu ViewsModeMenu ViewsPivotChartView ViewsPivotChartView ViewsPivotTableView ViewsReportView ViewsSwitchToDefaultView
VisualBasic WindowMoreWindowsDialog WindowNameGoesHere WindowsArrangeIcons WindowsCascade WindowsDataEntry WindowSplit WindowsSwitch WindowsTileHorizontally WindowsTileVertically WindowUnhide Zoom100 ZoomFitToWindow ZoomOnePage ZoomPrintPreviewExcel
The XML code listed next (the Ribbon name is TurnOff) turns off the standard Home tab, and removes the Export group from the External Data tab:
<customUI xmlns= http://schemas.microsoft.com/office/2006/01/customui> <ribbon startFromScratch=false> <tabs> <tab idMso=TabHomeAccess visible=false> </tab> <tab idMso=TabExternalData visible=true> <group idMso=GroupExport visible=false> </group> </tab> </tabs> </ribbon> </customUI>
NOTE
In order to see this (or any) custom Ribbon in an Access database, you need to select it in the Ribbon Name drop-down list in the Access Options dialog, as shown in Figure 15.8.
541
Part III
Figure 15.5 shows the External Data tab, without the Export group.
FIGURE 15.5 The External Data tab without the Export group.
VBA Code To open the References dialog, select References from the Tools menu in the Visual Basic TIP window.
To run commands from controls on your customized Ribbon, you need to write a callback procedure for each Ribbon command button. First, set a reference to the Office 12.0 object library in the References dialog (see Figure 15.6); it is needed to support various Ribbon-related objects in the code.
The sample Test Ribbon database contains two procedures to be run from Ribbon buttons on the List Fields custom Ribbon; note the ByVal control As IRibbonControl argument, which links the procedure to the control:
Public Sub ListTableFields(ByVal control As IRibbonControl) On Error Resume Next
542
15
First, clear data from the old table of table and field names:
strTable = zstblTableAndFieldNames strReport = zsrptTableAndFieldNames DoCmd.SetWarnings False strSQL = DELETE * FROM & strTable DoCmd.RunSQL strSQL
Fill the table with table and field names, iterating through the databases TableDefs collection:
Set dbs = CurrentDb Set rst = dbs.OpenRecordset(strTable, dbOpenTable) For Each tdf In dbs.TableDefs strTable = tdf.Name If Left(strTable, 4) <> MSys Then Set flds = tdf.Fields For Each fld In flds strFieldName = fld.Name With rst .AddNew !TableName = strTable !FieldName = strFieldName !DataType = fld.Type !ValidationRule = fld.ValidationRule !Required = fld.Required .Update End With Next fld End If Next tdf rst.Close DoCmd.OpenTable strTable strTitle = Table filled strPrompt = Print report now? intReturn = MsgBox(strPrompt, vbQuestion + vbYesNo, _ strTitle) If intReturn = vbYes Then strReport = zsrptTableAndFieldNames DoCmd.OpenReport strReport End If ErrorHandlerExit: Exit Sub
543
Part III
ErrorHandler: MsgBox Error No: & err.Number _ & ; Description: & err.Description Resume ErrorHandlerExit End Sub Public Sub ListQueryFields(ByVal control As IRibbonControl) On Error Resume Next Dim strQueryName As String Dim qdf As DAO.QueryDef
First, clear data from the old table of query and field names:
strTable = zstblQueryAndFieldNames strReport = zsrptQueryAndFieldNames DoCmd.SetWarnings False strSQL = DELETE * FROM & strTable DoCmd.RunSQL strSQL
Fill the table with query and field names, iterating through the databases QueryDefs collection (only select queries will have their fields listed):
Set dbs = CurrentDb Set rst = dbs.OpenRecordset(strTable, dbOpenTable) For Each qdf In dbs.QueryDefs strQueryName = qdf.Name Debug.Print Query name: & strQueryName If Left(strQueryName, 4) <> MSys Then Set flds = qdf.Fields For Each fld In flds strFieldName = fld.Name With rst .AddNew !QueryName = strQueryName !FieldName = strFieldName !DataType = fld.Type !Required = fld.Required .Update End With Next fld End If Next qdf rst.Close DoCmd.OpenTable strTable
544
15
strTitle = Table filled strPrompt = Print report now? intReturn = MsgBox(strPrompt, vbQuestion + vbYesNo, _ strTitle) If intReturn = vbYes Then strReport = zsrptTableAndFieldNames DoCmd.OpenReport strReport End If ErrorHandlerExit: Exit Sub ErrorHandler: MsgBox Error No: & err.Number _ & ; Description: & err.Description Resume ErrorHandlerExit End Sub
Once you have created the XML code and stored it in the USysRibbons table, and written any needed callback procedures to run from command buttons on the Ribbon, you need to close the database and reopen it, to load the customized Ribbon(s). Then you have to select the Ribbon you want to use in the database, as described here:
1. Close the database, then reopen it. 2. Click the Office button, then the Access Options button (Figure 15.7).
545
Part III
3. Select the Current Database page and select the Ribbon you want to load from the Ribbon Name drop-down list, as shown in Figure 15.8.
4. Close the database and reopen it, and, as in Figure 15.9, now you should see the Ribbon customization.
FIGURE 15.9 The Listing Options tab created by the ListFields custom Ribbon.
546
15
5. To load another Ribbon, select it from the Ribbon Name drop-down list, and close and reopen the database.
NOTE
You may get an error message like the one shown in Figure 15.10 when reopening a database after creating or editing XML code. Note the line and column reference, which should help in figuring out what the problem is, even if the error description isnt much help. Sometimes it is as simple as a missing bracket.
FIGURE 15.10 An informative error message when loading Ribbon customization XML code.
Form Ribbons
You can also make form Ribbons, to replace or customize the built-in Ribbons. The Test Ribbon database has a sample form Ribbon, AddButtons. This Ribbon displays for a specific form, after selecting it in the Current Database page of the Access Options screen, and in the forms property sheet. The XML for the AddButtons Ribbon is shown in Figure 15.11.
FIGURE 15.11 The XML code for the custom AddButtons form Ribbon.
547
Part III
This code adds a custom tab called Custom Form Tab, with a single group and two command buttons. The Built-in Paste button runs the default Paste command (if there is something in the Clipboard to paste); the Click Me button pops up a simple message box. To display a custom Ribbon on a form, you need to select the Ribbon name in the forms RibbonName property, as shown in Figure 15.12.
548
15
FIGURE 15.13 A form custom Ribbon with two buttons, and the message box popped up by the Click Me button.
NOTE CAUTION
If you are attempting to install an add-in in Access 2007 running on Windows Vista, you may get the security warning shown in Figure 15.14. Some special techniques are needed to get Access Ribbon add-ins to work in Vista; see the Getting Your Add-ins to Work in Vista sidebar for details. This is not a problem when installing add-ins for Access 2007 running on Windows XP.
549
Part III
FIGURE 15.14 A security warning when attempting to install an Access add-in for Access 2007 running on Windows Vista.
CROSS-REF NOTE
See PC Magazine Windows Vista Security Solutions (Wiley, 2007) for more information on dealing with Vista security issues.
In Windows Vista, callback functions wont run from Ribbon buttons unless you include the name of the add-in database project before the function name, as I have done in the XML code listed below.
The technique for creating the Ribbon XML and storing it in a table is the same as for a regular Access 2007 database, as described in the previous section. The Extras (Ribbon) library database has only one add-in with three records in its USysRegInfo table, to load the ExtrasRibbon from the USysRibbons table. Its XML code is listed next:
<customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui> <ribbon startFromScratch=false> <tabs> <tab id=dbCustomTab label=Extras visible=true> <group id=dbListingGroup label=Listing Fields> <button id=btnListTableFields label=List Table Fields enabled=true imageMso=CreateTable size=normal onAction=Extras(Ribbon) .ListTableFields/> <button id=btnListQueryFields label=List Query Fields enabled=true imageMso=CreateQueryInDesignView size=normal onAction=Extras(Ribbon) .ListQueryFields/> <button id=btnOpenOptionsDialog label=Select Options enabled=true imageMso=CreateFormBlankForm size=normal onAction=Extras(Ribbon) .ExtrasOptions/>
550
15
</group> <group id=dbBackupGroup label=Database Backup> <button id=btnBackupFrontEnd label=Back up current database enabled=true imageMso=Copy size=normal onAction=Extras(Ribbon) .BackupFrontEndDB/> <button id=btnBackupBackEnd label=Back up back-end database enabled=true imageMso=Copy size=normal onAction=Extras(Ribbon) .BackupBackEndDB/> </group> </tab> </tabs> </ribbon> </customUI>
The USysRegInfo table with the Enable Extras Tab menu add-in records is shown in Figure 15.15.
FIGURE 15.15 The USysRegInfo table with a set of records for a single menu add-in.
ecause of Vistas new security features, you need to take a few more steps when creating and installing an Access 2007 add-in to get it to install and work in Vista. The first step is to run Access as an administrator. Right-click the MSACCESS.EXE file in the Office 12 subfolder under the Microsoft Office folder, and select Run as administrator:
continued
551
Part III
continued
you need
onAction=Extras (Ribbon).ListQueryFields/>
After making the above changes to the add-ins system tables, compile the add-ins code, and save and close it. Now you should be able to install the add-in and run its menu add-in(s), and its Ribbon buttons should work.
The Extras (Ribbon) add-in has only a single menu add-in: Enable Extras Tab, which runs a procedure (listed next) that copies the USysRibbons table into the calling database, and attempts to load the table.
552
15
CAUTION
Loading a table programmatically with the LoadCustumUI function doesnt always work; if the table is not automatically loaded, you can load it manually, as described earlier in this chapter (see Figure 15.8).
The basExtrasRibbon module also contains several callback procedures, which are the same as those in the standard Extras 2007.accda add-in, except for the ByVal control As IRibbonControl argument (plus one new procedure, for backing up a back end database).
TIP
How did I find the GUID for Office 12s object library? I found the path from the References dialog, then searched for it in the Registry, using the RegEdit utility; the GUID is on the line above the one listing the path.
Public Function LoadRibbons() Must be a function so it can be run from USysRegInfo table. On Error Resume Next Dim i As Integer Dim strRibbonName As String Dim strRibbonXML As String Set dbsCode = CodeDb Set dbsCalling = CurrentDb
Add a reference to the Office 12 object library (if there isnt one already).
Application.References.AddFromGuid _ {000C0126-0000-0000-C000-000000000046}, 1, 0
Copy the USysRibbons table to the calling database, after deleting any existing table of that name, if there is one.
Set dbsCalling = CurrentDb strCallingDb = CurrentDb.Name Set tdfsCalling = dbsCalling.TableDefs strTable = USysRibbons Set tdfCalling = tdfsCalling(strTable) DoCmd.SetWarnings False If tdfCalling Is Nothing Then Debug.Print strTable & not found; about to copy it DoCmd.CopyObject destinationdatabase:=strCallingDb, _ newname:=strTable, _ sourceobjectType:=acTable, _ sourceobjectname:=strTable Else
553
Part III
Load the Ribbon from the table record (if it has not already been loaded).
Application.LoadCustomUI _ customuiname:=strRibbonName, _ customuixml:=strRibbonXML rst.MoveNext Loop dbsCalling.Close Set dbsCalling = Nothing ErrorHandlerExit: Exit Function ErrorHandler: MsgBox Error No: & Err.Number _ & ; Description: & Err.Description Resume ErrorHandlerExit End Function
Once the add-in has been loaded, you can select Enable Extras Tab from the Add-ins menu on the Database Tools tab of the Ribbon, as shown in Figure 15.16.
554
15
FIGURE 15.16 Selecting a menu add-in to load a custom Ribbon from an Access add-in.
CROSS-REF
Close and reopen the database. If the ExtrasRibbon was not automatically loaded, select it manually, then close and reopen the database again; you should now see the Extras tab, as shown in Figure 15.17.
The Select Options button opens the dialog where various options can be selected; the other buttons run functions to list table or query fields, or back up the current database or its back end.
555
Part III
FIGURE 15.18 The Select Options dialog for selecting a backup folder and entering prefixes to exclude.
CAUTION
The Ribbon is a new feature for Office, and (not surprisingly) even in the release version of Access 2007 it is not entirely stable, especially when the buttons run procedures from an add-in. You may find that you have to repeatedly uninstall and reinstall an add-in, unload and reload a Ribbon, and (most of all!) repeatedly close and reopen a database to get a Ribbon that calls add-in procedures to work. Hopefully this instability will clear up in an upcoming patch or service release of Office 2007.
If you select the List Table Fields command, a table is filled with the names of tables and their fields, and a message box asks if you want to print the report bound to the table now, as shown in Figure 15.19:
556
15
FIGURE 15.19 A table filled with names of tables and their fields.
Selecting the Back up back end database command extracts the name of the back end database from the Connect string of a linked table, and presents an InputBox with a proposed save name, which can be edited as desired. The InputBox is shown in Figure 15.20
FIGURE 15.20 An InputBox with a proposed save name for a back-end database.
557
Part III
If you run this command in a database that has no linked tables, you will instead get the message shown in Figure 15.21:
FIGURE 15.21 A message when attempting to back up the back end for a database that has no linked tables.
The procedures for the List Table Fields, List Query Fields, Select Options, Back up current database, and Back up back end database buttons are similar to those in the Extras 2007.accda add-in, except that they use the ByVal control As IRibbonControl argument that is needed to run them from Ribbon buttons; their code is not listed here.
Summary
Although at first it may appear that the new Ribbon is not customizable, you can in fact customize it, at least by adding new tabs and groups, with controls to run your code. This chapter covered writing XML code to load a custom Ribbon, and VBA code for procedures to run from the custom Ribbon buttons, both in regular Access 2007 databases and in library databases for Access 2007 add-ins. The next chapter describes how to write Shared add-ins working with the Access Ribbon in Visual Basic 2005.
558
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
n Chapter 13, I described creating a VB 6 COM add-in to add extra functionality to Access. VB 6 (though still supported by Microsoft) is not the latest version of Visual Basic; if you want to use the latest version, that is VB 2005, included in Visual Studio 2005, available in several editions. There are significant differences between these versions of VB and some compatibility problems with Office 2007, but you can create Visual Studio 2005 add-ins that work with Access, though at present the job is much harder than it should be, and their functionality is limited, because the Visual Studio Tools for Office add-in does not yet include an Access add-in template. This chapter describes creating a simple Visual Studio Shared add-in for Access that will run in both Windows XP and Windows Vista.
IN THIS CHAPTER
Customizing the Ribbon with a Visual Studio 2005 Shared add-in A comparison of Access and Visual Studio add-ins
559
Part III
need to run Office install. In Windows Vista, first select Programs in the Control Panel, then Programs and Features, then select the Microsoft Office item, as shown in Figure 16.1. If you are running Windows XP, start by selecting the Add or Remove Programs applet in the Control Panel, then the Microsoft Office 2007 item.
On the next screen, shown in Figure 16.2, select the Change option, then Add or Remove Features for Vista, or the Add or Remove Features option for Windows XP. In the Installation Options dialog, drop down the Microsoft Office Access list; if the .NET Programmability Support item has a big red X, that means that it is not installed. To install it, drop down its list and select the Run from My Computer item (see Figure 16.3).
560
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
FIGURE 16.2 Selecting the Add or Remove Features option for changing Office in Windows XP.
FIGURE 16.3 Selecting the Run from My Computer option for .NET programmability support.
561
Part III
If you want to create add-ins for any other Office components, select Run from My Computer for their .NET Programmability Support components as well. After clicking Continue, Office installs the new features, and when it is done, you will get a success screen, depicted in Figure 16.4; click Close on this screen, and then close the Add/Remove Program applet.
FIGURE 16.4 The success screen after changing the Office configuration.
Although VB 2005 Express has an Upgrade Wizard, it isnt helpful in upgrading a VB 6 COM addin to VB 2005 because the Express edition of VB 2005 doesnt support creating add-ins. If you attempt to upgrade a VB 6 COM add-in with this wizard (or the similar wizard in Visual Studio 2005), all the project components will be upgraded except the critical Access Designer, and you will get a message Activex Designer AccessDesigner.Dsr was not upgraded in the Upgrade Report, as shown in Figure 16.5.
562
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
FIGURE 16.5 The Upgrade Report for a VB 6 COM add-in, showing that the Access Designer was not upgraded.
CROSS-REF
You might think that Visual Studio Tools for Office 2005 (VSTO) would be an appropriate tool for creating Access add-ins, when supplemented with the downloadable upgrade that supports Office 2007 after all, Access is part of Office. But this is not so. Though you can create add-ins for all the other major Office 2007 components, and some minor ones as well (see Figure 16.6), you cant create an Access add-in with VSTO, and therefore in this section I use Visual Studio 2005 for creating a Shared add-in (this is the new name for what was previously called a COM add-in). To create a Shared add-in that adds capability to Access, start by running Visual Studio 2005 and selecting File New Project. In the New Project dialog, select the Extensibility selection under the Other Project Types category, then select the Shared Add-in template. Enter the add-ins name and solution name; you can either accept the default location for the add-ins files or browse for a custom location, as I did in Figure 16.7.
563
Part III
FIGURE 16.6 The VSTO New Project Office screen, showing that Access add-in creation is not supported.
CAUTION
The add-ins name cant contain spaces or punctuation marks just letters and numbers. However, the solution name can contain spaces or punctuation marks.
After clicking OK, you will get a Welcome to the Add-in Wizard screen, as seen in Figure 16.8.
564
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
n order to run Visual Studio 2005 on Windows Vista, you need to install a Service Pack and then a hotfix. The first download to install is the Visual Studio 2005 Service Pack 1, which can be downloaded from http://msdn2.microsoft.com/en-us/vstudio/bb265237.aspx (in various versions depending on the edition of Visual Studio). The hotfix is called the Visual Studio 2005 Service Pack 1 Update for Windows Vista Beta; it can be downloaded at http://www.microsoft.com/downloads/details .aspx?familyid=fb6bb56a-10b7-4c05-b81c-5863284503cf&displaylang=en. After installing the service pack and hotfix, when you run Visual Studio 2005, you will probably get this message:
continued
565
Part III
continued If you click on the link, you will get a Web page with more links to pages with information about running Visual Studio 2005 on Vista:
For running Visual Studio 2005 on Vista, the link you need is the third one, Running with elevated administrator permissions. There is a lot of information on this page, but basically you only need one thing: when running Visual Studio in Vista, right-click its icon and select Run as administrator from its context menu:
566
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
Now you can create new projects or open existing projects, and work with them as in Windows XP, and your Visual Studio add-in Ribbons (and other customizations) will work in Access 2007 databases.
567
Part III
On the screen depicted in Figure 16.9, select Visual Basic as the programming language to use (it may be the only choice, depending on the language support you selected when installing Visual Studio).
On the next screen, shown in Figure 16.10, select Microsoft Access as the application host.
568
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
On screen 3 of the wizard (see Figure 16.11), enter the add-ins name and description (not the same as the project name and solution name entered on the first screen).
Next, as depicted in Figure 16.12, select add-in options during development, its best to check only the first checkbox, so only you (the developer) can work with the add-in.
569
Part III
The final page of the Shared Add-in Wizard (see Figure 16.13) gives a summary of the selected options.
On clicking Finish, the new add-in project is created, with all the necessary components, and the Connect class (the Connect.vb module) is displayed, as shown in Figure 16.14 (note the similarity to the Access Designer in the VB 6 COM add-in). To work with the references more easily, select Project Show All Files, and expand the References folder in the Solution Explorer at the right of the screen.
TIP
If you dont see the References folder in the Solution Explorer, select Show All Files from the Project menu to make the folder visible.
570
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
Next, add a reference to the Access Interoperability item. To add references, right-click the References folder and select Add Reference, then select the .NET tab, select Microsoft.Office.Interop.Access (as in Figure 16.15), and click OK. Figure 16.16 shows the solutions references in the Solution Explorer.
571
Part III
572
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
The next step is to add some Imports statements under the two default statements in the Declarations section of the class module, using IntelliSense (see Figure 16.17):
Imports Imports Imports Imports Extensibility System.Runtime.InteropServices Microsoft.Office.Core Access = Microsoft.Office.Interop.Access
FIGURE 16.17 Using IntelliSense to add an Imports statement to the Connect class module.
Next, replace the class-level variables (applicationObject and addInInstance), both declared as Object, with typed references, replacing Dim with Private (I also gave the applicationObject variable a more application-specific name, appAccess):
Private appAccess As Microsoft.Office.Interop.Access.Application Private addInInstance As Microsoft.Office.Core.COMAddIn
After adding statements and modifying the variables, there are some more modifications to be made to the code in the Connect class module. These changes are described in the next section.
573
Part III
In the OnConnection method, modify the rows that set the application (Access) and add-in variables as follows:
appAccess = DirectCast(application, _ Microsoft.Office.Interop.Access.Application) addInInstance = DirectCast(addInInst, _ Microsoft.Office.Core.COMAddIn)
Next, modify the two lines that set the Access and add-in variables as follows:
appAccess = DirectCast(application, Access.Application) addInInstance = DirectCast(application, Core.COMAddIn)
NOTE
If all this manual modification of the Shared add-in code is getting tedious, hopefully the next version (v. 3) of VSTO will include an Access template that will eliminate most of the hand-coding for support of Access Shared add-ins and the Ribbon.
After adding the Implements IRibbonExtensibility line, a new function stub should appear in the Connect class module, GetCustomUI.
NOTE
If you dont see the function stub, try doing a Save All. If it still doesnt appear, just type in the whole function.
Finally, open the Assembly.vb class from the Solution Explorer and add information about the addin (you dont have to fill in all the information), as shown in Figure 16.18. This completes the general changes to the Connect class module; now you need to add code for your add-ins specific functionality.
574
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
575
Part III
The XML code for the Ribbon in this add-in is listed here (see Chapter 15 for more detailed information on creating Ribbon XML code):
<?xml version=1.0 encoding=utf-8 ?> <customUI xmlns=http://schemas.microsoft.com/office/2006/01/customui> <ribbon startFromScratch=false> <tabs> <tab id=dbDemoTab label=Visual Studio Add-in visible=true> <group id=dbAccessObjectsGroup label=Create Access Objects> <button id=btnCreateTable label=Create New Table enabled=true imageMso=Table size=normal onAction=CreateTableInDesignView/> <button id=btnCreateForm label=Create New Form enabled=true imageMso=CreateFormInDesignView size=normal onAction=CreateNewForm/> <button id=btnCreateReport label=Create New Report enabled=true
576
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
Next, the XML file needs to be treated as a resource within the project, so you dont have to deal with a separate text file. To do this, select Ribbon.xml in the Solution Explorer, and select Embedded Resource as the value for the Build Action property in its properties sheet (see Figure 16.20).
FIGURE 16.20 Selecting Embedded Resource as the Build Action property of the Ribbon.xml file.
Next, select CreateObjects Properties from the bottom of the Project menu (if you are working with a different project, its name appears instead of CreateObjects). Click the Resources tab; if you have not already created a resources file, all you will see is the link shown in Figure 16.21.
Click the link to create a resources file; a datasheet appears, with columns for Name, Value, and Comment. Drag the Ribbon.xml file from the Solution Explorer to the Resources pane; now, as in Figure 16.22, you will see an icon for the Ribbon.xml file.
577
Part III
FIGURE 16.22 The Ribbon.xml file added to the projects resources file.
Each of the buttons in the Ribbon XML code needs its own procedure; the three button procedures, plus a standard add-in error handler (same as in the VB 6 COM add-in) are listed next:
Public Sub CreateNewTable(ByVal control As _ Microsoft.Office.Core.IRibbonControl) On Error GoTo ErrorHandler Dim strSQL As String Dim strTable As String Dim obj As AccessObject strTable = tblTest strSQL = CREATE TABLE & strTable & (FileDate DATE, _ & FileNumber LONG, FileName TEXT (100), _ & Current YESNO); Debug.Print(SQL Statement: & strSQL)
578
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
579
Part III
chk = appAccess.CreateControl(FormName:=frm.Name, _ ControlType:=Microsoft.Office.Interop.Access.AcControlType.acCheckBox, _ Section:=Microsoft.Office.Interop.Access.AcSection.acDetail, _ Left:=0, Top:=4000, Width:=2500, Height:=400) ErrorHandlerExit: Exit Sub ErrorHandler: AddInErr(Err) Resume ErrorHandlerExit End Sub Public Sub CreateNewReport(ByVal control As _ Microsoft.Office.Core.IRibbonControl) On Error GoTo ErrorHandler Dim Dim Dim Dim Dim Dim rpt txt lbl cbo lst chk As As As As As As Microsoft.Office.Interop.Access.Report Microsoft.Office.Interop.Access.TextBox Microsoft.Office.Interop.Access.Label Microsoft.Office.Interop.Access.ComboBox Microsoft.Office.Interop.Access.ListBox Microsoft.Office.Interop.Access.CheckBox
580
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
chk = appAccess.CreateReportControl(ReportName:=rpt.Name, _ ControlType:=Microsoft.Office.Interop.Access.AcControlType.acCheckBox, _ Section:=Microsoft.Office.Interop.Access.AcSection.acDetail, _ Left:=0, Top:=4000, Width:=2500, Height:=400) ErrorHandlerExit: Exit Sub ErrorHandler: AddInErr(Err) Resume ErrorHandlerExit End Sub Public Sub AddInErr(ByVal errX As ErrObject)
NOTE
The syntax for named constants is much more verbose in Visual Studio 2005 than in Access VBA or VB 6: Instead of (for example) acControlType, you need the full enum reference, Microsoft.Office.Interop.Access.acControlType.
If you turn on the Error List pane (View Error List), you will see a number of warnings about implicit conversion of a variable from Access control to a specific Access control type (see Figure 16.23). In general, you can clear up conversion warnings by using a conversion function in the code, but Visual Studio 2005 doesnt have any conversion functions for Access controls, so you just have to live with the warnings (the code will run fine).
581
Part III
If you are very skilled at writing code, and very lucky, at this point you could install and test the code, and everything would work perfectly. But its more realistic to expect that there will be some bugs in the code that will prevent the add-in from working, so its a good idea to do some debugging at this point, to save the time you would spend going through the installation and then finding that the add-in doesnt work as expected.
582
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
TIP
If the Install selection is disabled after a successful first build, select Rebuild; after that, Install should be enabled.
583
Part III
Selecting Install starts the awkwardly named Create ObjectsSetup Setup Wizard (see Figure 16.26).
584
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
Clicking Next gets you the Select Installation Folder screen (see Figure 16.27), where you can either accept the default location or browse for another location, and select whether the add-in is available to all users or just you.
On the Confirm Installation screen of the Setup Wizard shown in Figure 16.28, click Next to start the installation.
585
Part III
The next screen, omitted here, shows a progress bar while the solution is being installed. When the installation is complete, you will get the Installation Complete screen, depicted in Figure 16.29.
Generally speaking, after clicking Close on the last wizard screen, the add-in is installed automatically; however, if you dont see the custom Ribbon that the add-in should create in an Access 2007 database, you can install it manually. Open the Access Options dialog from the Office menu, and select the Add-ins page. At the bottom of the page click the Go button to the right of the COM Add-ins drop-down, as shown in Figure 16.30. The COM Add-ins dialog (shown in Figure 16.31) looks the same as in older versions of Access. If you see the add-in listed, just check it; otherwise, you can browse for it.
586
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
FIGURE 16.30 Opening the COM Add-ins dialog from the Add-ins page of the Access Options dialog.
587
Part III
Clicking the Create New Table button creates a new table with several fields of different data types, the Create New Form button creates a new form with the new table as its record source and several controls of different types, and the Create New Report button creates a new report, also with the new table as its record source and several controls of different types. Figure 16.33 shows the new table in design view.
588
Customizing the Access Ribbon with a Visual Studio 2005 Shared Add-in
16
Figure 16.34 shows the new form; the new report is similar.
NOTE
The form is called Form1 (or another number); it cant be renamed in code, either in the Visual Studio add-in or directly in Access VBA, because the Name property of the Form object is read-only when the form is created using the CreateForm method (and similarly for the CreateReport method).
Summary
After working with all three add-in types (Access, VB 6, and Visual Studio 2005), my conclusion is that Access add-ins have advantages over both VB 6 and Visual Studio 2005 add-ins, at least if you are running Windows XP. One of the most significant advantages of Access add-ins is that they are themselves Access databases, and this allows you to copy database objects from the code database to the calling database. If you need forms in a VB or Visual Studio add-in, you have to create them from scratch as VB or Windows forms; reports (in some versions of VB), can only be created using Crystal Reports. Another advantage of Access add-ins is that only an Access add-in can create a wizard or builder. And finally, Access add-ins use VBA code, so you dont need to learn a new programming dialect, just a few special techniques. However, if you want your Access add-ins to create custom Ribbons, there are some roadblocks at present. Getting the custom Ribbon to display, and to run add-in code, may require so much time spent in uninstalling and reinstalling the add-in, unloading and reloading the table, and closing
589
Part III
and reopening the database numerous times, that you get totally frustrated in trying to get the addins custom Ribbon to display (and its buttons to work). By contrast, Access add-ins that create menu add-ins and property builders work just fine once they are installed, and so do Ribbons and buttons created by VB 6 and Visual Studio add-ins. When you need to put a button somewhere other than on the Add-Ins tab, and you dont want to fiddle with getting Ribbon buttons to work from Access add-ins, you can create a VB or Visual Studio add-in both work very well with Ribbons. Another special consideration is running add-ins on Windows Vista at present, Access add-ins have problems with Vista security, while (at least if you have installed the hotfix mentioned in the Running Visual Studio 2005 in Windows Vista sidebar Visual Studio add-ins run fine in Vista. Hopefully, v. 3 of VSTO should (at long last!) include an Access template, which should greatly simplify the process of creating Shared add-ins for Access.
590
or many versions now, Windows has had its own scripting language, Windows Script Host, a dialect of Visual Basic Script (VBS). Windows Script Host (WSH) scripts can be run from the command line (for those versions of Windows that have a command line, click Start Run), by double-clicking the script file in an Explorer window, and also from the Windows Vista Task Scheduler, which is handy if you want to run a script automatically, on a regular schedule. One use for a WSH script is to create a database backup at regular intervals; another is to copy Word or Excel templates, or other supporting files, to the appropriate folder, as part of an Office application setup, when you dont want (or need) to create a full Install package. WSH scripts are also useful for working with files in a folder, doing tasks such as deleting or renaming files containing a certain prefix, suffix, or extension. This chapter describes how to create and modify WSH scripts, including sample scripts for some common uses.
IN THIS CHAPTER
Writing Windows Script Host scripts Using the Microsoft Script Editor and the VBS Help file Differences between VBS and VBA code Scripts for working with Office documents Scripts for working with files Using the Windows Vista Task Scheduler to run a backup script
591
Part III
You can open the MSE directly by double-clicking the MSE7.EXE file, and then open a script to edit from its File Open menu, but for convenience you may wish to pin the executable file to the Start menu, as shown in Figure 17.2. For even greater convenience, you can select the MSE as the program to use when opening VBS files. To do this, right-click a file with the .vbs extension in an Explorer pane, and select the Choose Default Program command (see Figure 17.3).
592
17
FIGURE 17.2 Pinning the MSE executable to the Windows Vista Start menu.
FIGURE 17.3 Choosing the MSE as the default program to use with VBS files.
593
Part III
NOTE
The Microsoft (r) Windows Based Script Host entry in the Recommended Programs group on the Open With dialog runs the script, rather than opening it for editing.
Curiously, the MSE is not shown in either the Recommended Programs or Other Programs group in the Open With dialog (shown in Figure 17.4), so you need to browse for it in the C:\Program Files\Common Files\Microsoft Shared\OFFICE12\ folder.
FIGURE 17.4 The Open With dialog for selecting the program to use when opening a VBS file.
Figure 17.5 shows the MSE executable being selected in the Open With . . . Browse dialog. Back on the main Open With dialog, Microsoft Script Editor is now visible in the Other Programs group, and you can select it and click OK to open the script in the MSE. After selecting MSE7.EXE as the file to use when opening a VBS file, you will see the MSE as a selection on the Open With menu (Figure 17.6).
594
17
FIGURE 17.5 Selecting the MSE executable as the file to use when opening VBS files.
FIGURE 17.6 Selecting the MSE for use in opening VBS files.
595
Part III
If you leave the Always use the selected program to open this kind of file checkbox checked when opening the script file, then double-clicking a VBS file will open MSE, and you will need to select Microsoft (r) Windows Based Script Host from the Open With menu to run the script; leaving this checkbox unchecked means that double-clicking the VBS script file will run the script, and you can select the MSE to edit the script from the Open With menu. I generally leave it unchecked, to make it easier to run scripts.
TIP
If you want to run scripts by double-clicking them, select Microsoft (r) Windows Based Script Host as the program to use when opening VBS files, and check the Always use the selected program to open this kind of file checkbox.
Figure 17.7 shows the new MSE entry on the Open With menu of a VBS file.
FIGURE 17.7 The Microsoft Script Editor selection on a VBS files Open With menu.
NOTE
If you have Visual Studio 2005 installed, you will see that program on the Open With menu, and you can open a script in Visual Studio; however, the Visual Studio editor (for VBScripts) is basically just a text editor with some color-coding, so I recommend using the MSE instead.
The MSE window (shown in Figure 17.8) has some similarities to the Visual Basic module window for an Access (or another Office program), and to the VB 6 editor.
596
17
For full details on using the MSE, open Help from the MSE menu, and select Microsoft Script Editor Help. There are several Help topics related to using the MSE, as shown in Figure 17.9.
WARNING
The MSEs Help file is web-based, so it is only available if you are connected to the Internet.
The Help file (script56.chm) is a compiled HTML Help file, which can be opened in both Windows XP and Windows Vista. Unlike the MSEs own Help file, the script56.chm file is available whether or not you are connected to the Internet, although you do have to have a connection to download the file.
597
Part III
For working with WSH scripts, the VBScript Language Reference and Windows Script Host Help books are of the most use; they are shown opened in the Help file in Figure 17.9, with the Windows Script Host Object Model Help topic selected.
For help on using the MSE itself, open its Help file from the Help button on the editors menu, then select Microsoft Script Editor Help to open a page of MSE-related Help topic selections, as shown in Figure 17.10. There is a good deal of overlap between the Help file available from the MSE and the one downloaded from the Scripting web page, but in my opinion the downloaded Help file is much easier to use, because of the hierarchical tree-type topic list, so if you have the option of opening this Help file in Windows XP, I recommend you use it; otherwise, if you are connected to the Internet, you can use the MSE Help file.
598
17
599
Part III
TABLE 17.1
Array Handling Collection Conditional Compilation Control Flow Conversion Data Types Date/Time DDE Debugging Declaration Error Handling File Input/Output Financial Object Manipulation Objects Operators Options Select Case Strings Using Objects
Option Base Declaring arrays with lower bound <> 0 Add, Count, Item, Remove Access to collections using ! character #Const #If...Then...#Else DoEvents GoSub...Return, GoTo On Error GoTo On...GoSub, On...GoTo Line numbers, Line labels CVar, CVDate Str, Val All intrinsic data types except Variant Type...End Type Date statement, Time statement LinkExecute, LinkPoke, LinkRequest, LinkSend Debug.Print End, Stop Declare (for declaring DLLs) Optional ParamArray Static Erl Error Resume, Resume Next All traditional Basic file I/O All financial functions TypeOf Clipboard Collection Like Deftype Option Base Option Compare Option Private Module Expressions containing Is keyword or any comparison operators Expressions containing a range of values using the To keyword. Fixed-length strings LSet, RSet Mid Statement StrConv Collection access using !
Though this table is useful, it doesnt cover all the differences between VBA and VBS code, nor does it identify the ones that are most likely to cause trouble when converting VBA code to VBS. When working with both Outlook VBS and WSH scripts I have found a number of differences between the two dialects of VB that need to be taken into account when writing VBS code (especially when converting VBA code to VBS). These differences are listed in Table 17.2.
600
17
TABLE 17.2
Only a very limited number of basic named constants are supported. Use the Object Browser opened from a VBA module window to locate the numeric equivalent of a named constant (in Figure 17.11 you can see that 40 is the numeric equivalent of the olContact named constant). Data typing of variables is not supported. Remove all data typing of declarations when converting code from VBA to VBS for example, instead of Dim appOutlook As Outlook.Application, in VBS the declaration is just Dim appOutlook. Only very limited error handling is supported, using On Error Resume Next. However, you can use various methods of the FileSystemObject to determine whether files exist before performing operations on them, as an alternative to VBA-type error handling for file operations. You can also determine whether or not an object has been set by checking whether it is equal to Nothing (for example, fil Is Nothing).
Variables can (and should) be declared as specific data types, for example Dim appOutlook As Outlook.Application. Full error handling is supported, typically using On Error GoTo ErrorHandler with a section specifying the error handling to be done.
You can use Debug.Print statements to write data from variables to the Immediate window as the code is executing, for debugging purposes. In a For Each . . . Next statement, use the variable in the Next line (for example, Next fil). The Nz function is supported (in Access VBA code, or other VBA code if there is a reference to the Access object library). To comment out a multi-line code statement using the underscore line continuation character, you only need to put an apostrophe at the beginning of the first line.
Debug.Print is not supported; you must use MsgBox statements instead, which hold up code execution until you click OK (after the script is running correctly, comment out the MsgBox statements).
In a For Each . . . Next statement, omit the variable in the Next line (for example, Next). The Nz function is not supported, use the IsNull function, or check that a value is equal to , as substitutes.
To comment out a set of lines using the line continuation character, you must put an apostrophe at the beginning of each line.
601
Part III
n VBA code, you can use GetObject to set a reference to an existing instance of an application, such as Word or Excel, to avoid creating numerous instances running in the background (you can see several WINWORD.EXE processes in the Task Manager in the following figure). Typically, an error handler detects whether GetObject set the reference, and if not, CreateObject is used instead. Although the GetObject function worked correctly in earlier versions of Windows, unfortunately, this technique doesnt work when you run VBS scripts in Windows Vista. Whether you use GetObject (with a slightly different syntax), or CreateObject, and whether or not there is a running instance of an application, a new instance is created every time the script is run.
602
17
FIGURE 17.11 Viewing the numeric equivalents of named constants in the OlObjectClass Outlook enum in the Object Browser.
Useful Scripts
You can use WSH scripts for a number of different purposes, which are described in the following sections.
Setup Scripts
The CopyTemplateUser.vbs script copies a template called Test.dot from the current folder (the folder where the script file is located) to the default User Templates folder; the CopyTemplateWorkgroup.vbs script copies Test.dot from the current folder to the Workgroup Templates folder (if one has been selected). Each one puts up informative message boxes as the code runs. (The MsgBox lines can be commented out after verifying that the script works correctly.)
603
Part III
o view (or set) the User Templates and Workgroup Templates path, click the Office button in any Word document and click the Word Options button:
604
17
The File Locations dialog, where you can set the template paths.
605
Part III
To test one of these scripts, copy the Test.dot template to the same folder as the script (you can also copy any Word template, and rename it Test.dot.) Right-click the CopyTemplateUser.vbs file in an Explorer window and select Microsoft (r) Windows Based Script Host from the Open With menu, as shown in Figure 17.12.
If you have not commented out this message box, you will first see a message listing the template name and current path (Figure 17.13).
FIGURE 17.13 A message box listing the template name and path for copying a Word template.
Next, if the copy is successful, you will get the message (the path may be different on your system) shown in Figure 17.14.
606
17
FIGURE 17.14 A success message after copying the template to the User Templates path.
If the copy could not be done, because Test.dot could not be located in the current folder, you will instead get the message shown in Figure 17.15.
FIGURE 17.15 A message indicating that the template to be copied could not be found.
NOTE
When you open the MSE, you may see a script open in the code pane, and other scripts listed in the Project Explorer; these are the scripts that were recently opened. If you see the script you want to edit in the Project Explorer, just select it.
The text of CopyTemplateUser.vbs is listed next. There are several commented-out MsgBox statements in this script; if you want to display these message boxes, uncomment these lines by removing the apostrophe at the beginning of the line:
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim strTemplate strUserTemplatePath appWord fso strScriptPath strScriptName strScriptNameAndPath fil strPrompt strTemplatePath
strScriptName = WScript.ScriptName
607
Part III
Get the path of the current script, extracting it from the ScriptFullName property:
strScriptNameAndPath = WScript.ScriptFullName strScriptPath = Mid(strScriptNameAndPath, 1, _ Len(strScriptNameAndPath) - Len(strScriptName)) strTemplate = Test.dot
Get the User Templates path from the Word Options dialog, using the DefaultFilePath property with the argument 2 (most named constants cant be used in VBS):
Set appWord = WScript.CreateObject(Word.Application) strUserTemplatePath = appWord.Options.DefaultFilePath(2) _ & \ MsgBox User templates path: & strUserTemplatePath If strUserTemplatePath <> \ Then Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) strTemplatePath = strScriptPath & strTemplate MsgBox Source template and path: & strTemplatePath
Try to locate the template file, using the FileSystemObjects GetFile method; quit if it is not found:
Set fil = fso.GetFile(strTemplatePath) If fil Is Nothing Then strPrompt = Cant find & strTemplate & _ in & strScriptPath & folder; canceling MsgBox strPrompt, vbCritical + vbOKOnly Quit Else
Copy the template to the User Templates folder, using the Copy method of the File object:
fil.Copy strUserTemplatePath, True MsgBox strTemplate & copied to _ & strUserTemplatePath End If Else strPrompt = _ User template path not selected; canceling MsgBox strPrompt, vbCritical + vbOKOnly End If
The CopyTemplateWorkgroup.vbs is similar, but it has some more error handling, to deal with the situation where the Workgroup Templates path has not been selected in Word:
Dim Dim Dim Dim strTemplate strWorkgroupTemplatePath appWord fso
608
17
strScriptName = WScript.ScriptName
Get the path of the current script, extracting it from the ScriptFullName property:
strScriptNameAndPath = WScript.ScriptFullName strScriptPath = Mid(strScriptNameAndPath, 1, _ Len(strScriptNameAndPath) - Len(strScriptName)) strTemplate = Test.dot strCurrentPath = strScriptPath & strTemplate
Get the User Templates path from the Word Options dialog, using the DefaultFilePath property with the argument 2 (most named constants cant be used in VBS):
Set appWord = WScript.CreateObject(Word.Application) strWorkgroupTemplatePath = _ appWord.Options.DefaultFilePath(3) & \ If strWorkgroupTemplatePath <> \ Then Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) strTemplatePath = strScriptPath & strTemplate MsgBox Source template and path: & strTemplatePath
Try to locate the template file, using the FileSystemObjects GetFile method; quit if it is not found:
Set fil = fso.GetFile(strTemplatePath) If fil Is Nothing Then strPrompt = Cant find & strTemplate & _ in & strScriptPath & folder; canceling MsgBox strPrompt, vbCritical + vbOKOnly Quit Else
Copy the template to the Workgroup Templates folder, using the Copy method of the File object:
fso.CopyFile strCurrentPath, _ strWorkgroupTemplatePath, True MsgBox strTemplate & copied from & _ strCurrentPath & to _ & strWorkgroupTemplatePath End If
609
Part III
Else strPrompt = _ Workgroup template path not selected; canceling MsgBox strPrompt, vbCritical + vbOKOnly End If
One or the other of these files (with the appropriate template name replacing Test.dot) would be useful as part of an Office application that includes one or more Word templates; all the user needs to do to install the template(s) in the right location is to run the script. If you need to copy a set of templates to the User Templates or Workgroup Templates folder, you dont need to hard-code the template names, so long as the templates you want to copy are the only templates in the current folder. The CopyAllTemplates.vbs script listed next copies all the Word 2007 (*.dotx) templates in the current folder to the User Templates folder:
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim strTemplate strUserTemplatePath appWord fso strScriptPath strScriptName strScriptNameAndPath fil filTemplate strPrompt strTemplatePath
Get the User Templates path from the Word Options dialog, using the DefaultFilePath property with the 2 argument:
Set appWord = WScript.CreateObject(Word.Application) strUserTemplatePath = appWord.Options.DefaultFilePath(2) _ & \ MsgBox User templates path: & strUserTemplatePath If strUserTemplatePath <> \ Then Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) Set fld = fso.GetFolder(strScriptPath)
610
17
Copy all Word 2007 (*.dotx) templates from the current folder to User Templates folder, with a message box for each one (comment out the MsgBox line to suppress these messages):
lngCount = 0 For Each fil In fld.Files If Right(fil.Name, 4) = dotx Then strTemplate = fil.Name fil.Copy strUserTemplatePath, True MsgBox strTemplate & copied to _ & strUserTemplatePath lngCount = lngCount + 1 End If Next Else strPrompt = _ User template path not selected; canceling MsgBox strPrompt, vbCritical + vbOKOnly End If
NOTE
The sample database for this chapter is Northwind.accdb. This is a version of the sample Northwind database, with its tables renamed according to the Leszynski Naming Convention.
Office Scripts
PrintReport.vbs demonstrates another use of scripts; it prints a report from Northwind.accdb, without the need to open the database. This could be handy if you need to print out labels on a regular basis. The code for this script is listed as follows:
Dim appAccess Dim strDBName Set appAccess = _ WScript.CreateObject(Access.Application) Set fso = _ WScript.CreateObject(Scripting.FileSystemObject)
611
Part III
TIP
If the MSE is already open, you can open a new script for editing by dragging the script file from an Explorer pane into the MSE window.
Northwind.accdb has a form for selecting an invoice to print by order number. But you might want to be able to quickly print an invoice without opening the database; the PrintInvoice.vbs script listed next does just this:
Dim Dim Dim Dim Dim appAccess strDBName lngInvoiceNo strTitle strPrompt
Set appAccess = _ WScript.CreateObject(Access.Application) Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) strDBName = D:\Documents\Northwind.accdb On Error Resume Next appAccess.OpenCurrentDatabase strDBName Set fil = fso.GetFile(strDBName) If fil Is Nothing Then strPrompt = Cant find & strDBName & _ ; canceling MsgBox strPrompt, vbCritical + vbOKOnly Quit Else strTitle = Select invoice strPrompt = Select an invoice to print (10248 - 11077) lngInvoiceNo = CLng(InputBox(strPrompt, strTitle)) MsgBox Invoice No.: & lngInvoiceNo
Run a lengthy SQL statement to create a table for use as a report record source:
strSQL = SELECT DISTINCT qryInvoices.OrderID, _ & qryInvoices.ShipName, qryInvoices.ShipAddress, _ & qryInvoices.ShipCityStateZip, _
612
17
& qryInvoices.ShipCountry, _ & qryInvoices.CustomerID, qryInvoices.CompanyName, _ & qryInvoices.BillToAddress, _ & qryInvoices.BillToCityStateZip, _ & qryInvoices.BillToCountry, _ & qryInvoices.Salesperson, _ & qryInvoices.OrderDate, qryInvoices.RequiredDate, _ & qryInvoices.ShippedDate, qryInvoices.Shipper, _ & qtotInvoiceDetails.SumOfExtendedPrice _ & AS Subtotal, _ & qryInvoices.Freight, [SumOfExtendedPrice] _ & + [Freight] _ & AS Total INTO tmakInvoice _ & FROM qryInvoices _ & INNER JOIN qtotInvoiceDetails _ & ON qryInvoices.OrderID = _ & qtotInvoiceDetails.OrderID _ & WHERE qryInvoices.OrderID = & lngInvoiceNo appAccess.DoCmd.SetWarnings False appAccess.DoCmd.RunSQL strSQL appAccess.DoCmd.OpenReport rptSingleInvoice End If Set appAccess = Nothing
When this script is run, an input box pops up where you can enter an invoice number, as shown in Figure 17.16.
After you enter the invoice number and click OK, a SQL statement is run to create a make-table query that is part of the record source of rptSingleInvoice, and that report is printed. The report is shown in Figure 17.17.
613
Part III
As a quick alternative to opening Word or Excel and selecting the correct template, you can also use a WSH script to create a new Word document or Excel worksheet based on a template. This technique can be useful when users have problems selecting the correct template to use when creating a new Word document or Excel worksheet. The New Document from Template.vbs script listed next opens a new Word document based on a Word 97-2003 template located in the main User Templates folder:
Dim Dim Dim Dim Dim appWord strTemplatePath strUserTemplatePath strTemplate docs
614
17
Get the User Templates path from the Word Options dialog, using the DefaultFilePath property with the argument:
strUserTemplatePath = appWord.Options.DefaultFilePath(2) _ & \ MsgBox User templates path: & strUserTemplatePath If strUserTemplatePath <> \ Then Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) strTemplatePath = strUserTemplatePath & strTemplate MsgBox Source template and path: & strTemplatePath On Error Resume Next Set fil = fso.GetFile(strTemplatePath) If fil Is Nothing Then strPrompt = Cant find & strTemplate & _ in & strTemplatePath & folder; canceling MsgBox strPrompt, vbCritical + vbOKOnly Quit Else Set appWord = WScript.CreateObject(Word.Application) appWord.Visible = true Set docs = appWord.Documents docs.Add(strTemplate) End If Else strPrompt = _ User template path not selected; canceling MsgBox strPrompt, vbCritical + vbOKOnly End If
The New Worksheet from Template.vbs script listed next opens a new Excel worksheet based on an Excel 2007 template located in the main User Templates folder:
Dim Dim Dim Dim Dim Dim appExcel strTemplatePath strTemplate bks wkb wks
615
Part III
Get the User Templates path from the Word Options dialog.
Set appWord = WScript.CreateObject(Word.Application) strUserTemplatePath = appWord.Options.DefaultFilePath(2) _ & \ Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) strTemplatePath = strUserTemplatePath & strTemplate MsgBox Source template and path: & strTemplatePath On Error Resume Next Set fil = fso.GetFile(strTemplatePath) If fil Is Nothing Then strPrompt = Cant find & strTemplate & _ in & strScriptPath & folder; canceling MsgBox strPrompt, vbCritical + vbOKOnly Quit Else Set appExcel = WScript.CreateObject(Excel.Application) appExcel.Visible = True Set bks = appExcel.Workbooks Set wkb = bks.Add(strTemplatePath) Set wks = wkb.Sheets(1) wks.Activate End If
Miscellaneous Scripts
The next script is one I made to automate the process of deleting temporary files from Audible.com downloads. These files are sometimes not automatically deleted and have to be deleted manually, which is a nuisance; the Delete Audible Files.vbs script is listed as follows:
Dim Dim Dim Dim Dim fso strPath strFile strFilePath blnFound Set fso = CreateObject(Scripting.FileSystemObject) blnFound = False strPath = E:\Audible\Bin\ strFile = Debug.log strFilePath = strPath & strFile If fso.FileExists(strFilePath) Then fso.DeleteFile strFilePath blnFound = True End If
616
17
strFile = aadownload.log strFilePath = strPath & strFile If fso.FileExists(strFilePath) Then fso.DeleteFile strFilePath blnFound = True End If strFile = aaschedule.log strFilePath = strPath & strFile If fso.FileExists(strFilePath) Then fso.DeleteFile strFilePath blnFound = True End If strFile = aasubsschedule.log strFilePath = strPath & strFile If fso.FileExists(strFilePath) Then fso.DeleteFile strFilePath blnFound = True End If If blnFound = True Then MsgBox Deleted Audible temp files Else MsgBox No Audible temp files found End If
The script first sets a blnFound variable to False (it would be a Boolean variable, if variables could be declared with data types in VBS). Then the script uses the FileExists method of the FileSystemObject to determine whether a file exists, and delete it if so, setting blnFound to True. At the end of the code, depending on whether the blnFound variable is True or False, a message box appears saying either Deleted Audible temp files or No Audible temp files found. You probably dont need the Delete Audible Files.vbs script, but you may find the Delete Temp Files.vbs script useful. This script uses the GetSpecialFolder method of the FileSystemObject, with the 2 argument, to set a fldTemp variable to the Temp file folder, and puts up a message box that asks if you want to delete all files in it; if you click the Yes button, the script attempts to delete all the files in that folder (the On Error Resume Next statement goes to the next file if a file cant be deleted, because it is in use):
Dim Dim Dim Dim Dim Dim fso fldTemp fil n intResult strPrompt
617
Part III
Set fso = CreateObject(Scripting.FileSystemObject) Set fldTemp = fso.GetSpecialFolder(2) strPrompt = Delete all files in & fldTemp & ? intResult = MsgBox (strPrompt, vbQuestion + vbYesNo) If intResult = 6 Then On Error Resume Next n = 0 For Each fil in fld.Files fil.Delete n = n + 1 Next MsgBox Approximately & n _ & temp files deleted from & fldTemp End If
The next script, Rename Files.vbs, renames figure files in a folder in a specific manner; the strNewFileName = Mid(fil.Name, 9) line of the script can be modified to rename files as needed:
Dim Dim Dim Dim Dim Dim Dim Dim fld lngCount fso strScriptPath strScriptName strScriptNameAndPath fil strPrompt
strScriptName = WScript.ScriptName
Get the path of the current script, using the ScriptFullName property:
strScriptNameAndPath = WScript.ScriptFullName strScriptPath = Mid(strScriptNameAndPath, 1, _ Len(strScriptNameAndPath) - Len(strScriptName)) On Error Resume Next lngCount = 0 Set fso = CreateObject(Scripting.FileSystemObject) Set fld = fso.GetFolder(strScriptPath) For Each fil In fld.Files
Check the first character and extension (modify as needed for your requirements):
If Left(fil.Name, 1) = - And _ Right(fil.Name, 3) = tif Then
618
17
WARNING
A VBS script run from the Windows Explorer can have spaces in its name, but if you intend to run a script from the Task Scheduler, its name cant contain spaces. You wont get an error when selecting the script, but when the Task Scheduler attempts to run it, you will get an error and it wont run.
Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim
fso fil strPrompt strDBPath strDBName strDBNameAndPath strDBNameBackup strScriptName strScriptNameAndPath strBackupNameAndPath
619
Part III
strDBNameBackup = Left(strDBName, Len(strDBName) - 4) _ & Backup for & FormatDateTime(Date(), 1) _ & .mdb MsgBox Backup name: & strDBNameBackup strBackupNameAndPath = strDBPath & strDBNameBackup
Check that the database is in the specified folder, and quit if it is not found:
On Error Resume Next Set fso = _ WScript.CreateObject(Scripting.FileSystemObject) Set fil = fso.GetFile(strDBNameAndPath) MsgBox Error #: & Err If fil Is Nothing Then strPrompt = Cant find & strDBName & _ in & strDBPath & folder; canceling MsgBox strPrompt, vbCritical + vbOKOnly Quit Else
You can run this script manually, but for assurance that a backup will be made every day, you can schedule the script to run from the Windows Task Scheduler. To open the Task Scheduler, first open the Control Panel, and select the Administrative Tools program group (Figure 17.18). In the Administrative Tools program group, select the Task Scheduler, as shown in Figure 17.19. If you get a UAC message, click Continue.
620
17
FIGURE 17.18 Opening the Administrative Tools program group in the Control Panel.
FIGURE 17.19 Selecting the Task Scheduler in the Administrative Tools program group.
621
Part III
In the Task Scheduler window, select Create Basic Task (Figure 17.20).
Next, as displayed in Figure 17.21, enter the name and description of the task.
622
17
On the next screen (Figure 17.22), make a selection for when the task should be run (choose Daily to run it every day).
On the screen shown in Figure 17.23, enter the time to run the script.
623
Part III
Next, specify an action to run at the specified time. Selecting Start a program runs a script (see Figure 17.24).
On the Start a Program screen, depicted in Figure 17.25, browse for the script to run.
624
17
The final screen of the Task Scheduler Wizard displays a success message, shown in Figure 17.26.
FIGURE 17.26 The final success message indicating that a task has been scheduled.
Now the Daily Database Backup.vbs script will be run at 9 PM every day without user intervention. When it is run, you will get several messages showing the name of the database to be copied, the name of the copy of the database (shown in Figure 17.27), and a final message showing where the copy was made. You will probably want to comment out these message boxes after verifying that the script runs.
FIGURE 17.27 A message from a VBS script run from the Task Scheduler.
625
Part III
Summary
Although not part of Office, WSH scripts are a useful addition to VBA code running from within Office applications, for tasks such as running daily backups, ensuring that users make documents and worksheets from the right templates, and copying setup files to the right folder. Because these scripts use a dialect of VBScript that will run in the last few versions of Windows as well as Vista, they are very useful when you need to perform an action, or a series of actions, in different boot partitions or on different computers, running different versions of Windows.
626
IN THIS CHAPTER
SQL Server 2005 versus SQL Server 2005 Express Configuring SQL Server 2005 Preparing an Access database for upsizing Using the Upsizing Wizard to upsize an Access database to SQL Server 2005 Linking an Access front end to data in a SQL Server database
NOTE
AdventureWorks SQL.accdb (Access front end to SQL Server tables) Basic Northwind v 1 (linked SQL Server tables).accdb (Access front end to SQL Server tables) Basic Northwind.accdb (Access database for upsizing) Basic Northwind CS.adp (Access project front end for SQL Server tables)
In Windows Vista and Office 2007, it isnt easy to connect an Access frontend database to a SQL Server back end. In Office 2003 running on Windows XP, using the Upsizing Wizard to convert an Access database to a SQL Server back end was easy even trivial. This task is now a major chore because you need to work through a long list of SQL Server settings and then
627
Part III
Windows security settings just to be able to connect to SQL Server before you even attempt to connect Access to SQL Server or upsize an Access database. This chapter helps you navigate through the SQL Server and Windows settings you need to do several SQL Server-related tasks: n Convert an Access database to SQL Server for use as a back end (this is known as upsizing the database) n Create a client/server application with a SQL Server back end and an Access project front end n Link an Access database to tables in a SQL Server database
FIGURE 18.1 Microsoft SQL Server 2005 listed in Programs and Features.
628
18
If you dont have SQL Server 2005 installed, you can download the Express version free, from http://msdn.microsoft.com/vstudio/express/sql/. Compared to the full version, SQL Server 2005 Express (SSE) has a number of limitations, but it is fine for experimenting with SQL Server as a back end, or developing small applications. SSE includes all the core database features of SQL Server 2005, but it lacks support for enterprise features. If you need to create an enterprise application, you will need one of the other editions of SQL Server 2005, but for individual or small business use, SSE is very suitable.
NOTE
For a full comparison of the features of the five editions of SQL Server 2005, see the tables here: www.microsoft.com/sql/prodinfo/features/comparefeatures.mspx.
SSE is an upgrade to the Microsoft Database Engine (MSDE), which was included in the higherend editions of previous Office versions. Compared to MSDE, SSE has several enhancements: n The maximum database size has doubled, from 2GB to 4GB. n MSDEs limitation on the number of concurrent users to five (with performance degrading significantly if there were more users) has been removed. n The SQL Server Management Studio (included with SSE) gives you a user-friendly interface for working with saved queries and stored procedures, as well as various administrative tasks. n The new XCopy deployment feature allows you to copy a database file to another computer, even if the database is not open. Other users can then connect to the database copy using the AttachDBFileName connection string argument.
NOTE
SQL Server 2005 must be upgraded to Service Pack 2 to work with Access 2007. If you have an earlier version, when you first run SQL Server 2005, or attempt to connect to it, you will get a message advising you of the need to upgrade, with a reference to a web page with the SP2 upgrade links for both SQL Server 2005 and SSE.
629
Part III
o make hidden tables visible, click File Access Options, select the Current Database page, and click the Navigation Options button:
The Navigation Options button on the Current Database page of the Access Options dialog.
On the Navigation Options dialog, check the Show Hidden Options checkbox:
630
18
You may have code in an Access database that was originally created many versions ago, but you havent upgraded it because it still runs. Before upsizing to SQL Server, you should make sure that all your code is up to date, because SQL Server is much less forgiving than Access. I made a sample database for this chapter, Basic Northwind.accdb, based on the old Northwind sample database that came with several previous versions of Access. Because Northwind was originally created many versions ago, and has only been minimally upgraded over Access versions, there is a good deal of old code in this database some of it very old code indeed: Access 95 or earlier. Before upsizing, I took the opportunity to update all the code to the current syntax. For example, the old Northwind code uses the IsLoaded function, provided in a module. Many versions of Access ago, this function was needed, but since Access 2000, you dont need a special function to check whether a form is loaded just use the IsLoaded property of the form, as an item in the AllForms collection. Here is some typical code for returning to the main menu, as used in a standard Form_Close event procedure:
Dim prj As Object Set prj = Application.CurrentProject If prj.AllForms(fmnuMain).IsLoaded = True Then Forms![fmnuMain].Visible = True Else DoCmd.OpenForm fmnuMain End If
Some features that are supported in Access applications wont survive upsizing, unfortunately. Functions called from calculated expressions in queries are not supported in SQL Server, so when you upsize a query that uses functions in calculated field expressions, you will get an error. I recommend removing the functions from query calculated expressions before upsizing; after the database is upsized, you can modify the corresponding stored procedure or user-defined function as needed in a way that will work in SQL Server.
631
Part III
ou can download a set of books on SQL Server 2005 from www.microsoft.com/technet/ prodtechnol/sql/2005/downloads/books.mspx.
Once downloaded and installed, you can open the SQL Server Books Online from the newly created Documentation and Tutorials group under the Microsoft SQL Server 2005 program group:
632
18
The books include documentation for both the regular version of SQL Server 2005 and SSE:
Server (or SSE) available as a back end for Access. By default, when SQL Server (or SSE) is installed, it may not be set to run on startup. To ensure that SQL Server is running and available, follow these steps:
1. Open the SQL Server Configuration Manager from the SQL Server (or SSE) group on the Start menu, as shown in Figure 18.2. 2. Select SQL Server 2005 Services in the left pane of the SQL Server Configuration Manager (Figure 18.3). In Vista, you may get a User Account Control (UAC) message. If so, just click Continue.
633
Part III
FIGURE 18.2 Selecting the SQL Server Configuration Manager item on the Start menu.
FIGURE 18.3 Selecting the SQL Server 2005 Services page in the SQL Server Configuration Manager.
634
18
3. As depicted in Figure 18.4, right-click the SQL Server (SQLEXPRESS) row and select Properties.
NOTE
The selection is SQL Server (SQLEXPRESS) even if you are running the regular edition of SQL Server 2005.
4. Select the Service tab in the properties sheet. 5. If the start mode is not automatic, drop down the Start Mode list and select Automatic, as in Figure 18.5.
FIGURE 18.5 Selecting the Automatic start mode for SQL Server.
635
Part III
6. Click OK. Next, right-click the SQL Server Browser row and select Properties. 7. Click the Service tab and set the Start Mode attribute to Automatic if needed, similar to step 5. 8. If you had to set the start mode to Automatic, and the state of the Browser was stopped, start it manually by right-clicking the SQL Server Browser row and selecting Start (Figure 18.6).
9. You will get a progress bar (see Figure 18.7) as the SQL Server service is started.
636
18
Next, you must enable network access this is required even if you are connecting to SQL Server on the same computer.
1. Start by expanding the SQL Server 2005 Network Configuration folder in the left pane of the SQL Server Configuration Manager, and select the Protocols for SQLEXPRESS folder under it. 2. Enable the TCP/IP protocol from its right-click menu (Figure 18.8).
3. You will get a message, shown in Figure 18.9, that you have to stop and restart the service. You can do this by right-clicking SQL Server (SQLEXPRESS) in the SQL Server 2005 Services folder and selecting Restart, or by shutting down and restarting Windows.
4. If your connection makes use of the Named Pipes protocol, you can enable it in a similar fashion.
637
Part III
Next, you have to configure SQL Server to allow remote connections (for some reason, this is necessary even if you have installed SQL Server on the same computer as Access):
1. Select SQL Server Surface Area Configuration (see Figure 18.10) from the Configuration Tools group in the Microsoft SQL Server 2005 program group. If you are running Vista, you may get a message that the program has known compatibility issues. If so, you need to install the SP2 service pack for SQL Server 2005.
FIGURE 18.10 The SQL Server 2005 Surface Area Configuration dialog.
2. Click the Surface Area Configuration for Services and Connections item (see Figure 18.10). 3. Expand the Database Engine item (if necessary) and click Remote Connections. 4. Select Local and Remote connections, and select the appropriate protocol, usually Using TCP/IP only (Figure 18.11).
638
18
5. When you close this dialog, you will be back on the SQL Server 2005 Surface Area Configuration dialog. If needed, click the other link (Surface Area Configuration for Features) and enable any features you plan to use (Figure 18.12).
6. Restart SQL Server by selecting Restart from the right-click menu for SQL Server (SQLEXPRESS) in the SQL Server 2005 Services folder.
639
Part III
Windows XP
Follow these steps to allow access to SQL Server through the Windows XP firewall:
NOTE
If you are using another firewall, the steps needed to allow access to SQL Server will be somewhat different.
1. Open the Security Center from the Control Panel, and click the Windows Firewall item (see Figure 18.13).
2. Click the Exceptions tab. 3. Click the Add Programs button. 4. SQL Server will probably not appear on the list of programs available for selection on the Add a Program screen, so click the Browse button, as shown in Figure 18.14.
640
18
FIGURE 18.14 The Add a Program dialog for selecting an exception to the Windows XP firewall.
5. First, browse for the SQL Server file sqlservr.exe, which is probably located in C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn (Figure 18.15).
FIGURE 18.15 Selecting the SQL Server executable for exclusion from the Windows XP firewall.
6. After clicking Open, sqlserver.exe will be listed in the Add a Program dialog, as seen in Figure 18.16.
641
Part III
FIGURE 18.16 The SQL Server executable in the Add a Program dialog.
7. Click OK, and you should see sqlserver.exe in the List of Exceptions. 8. Next, browse for the SQL Server Browser service, file name sqlbrowser.exe, probably located in C:\Program Files\Microsoft SQL Server\90\Shared, and click OK to add it to the List of Exceptions (Figure 18.17).
FIGURE 18.17 The SQL Server executable and browser in the list of firewall exceptions.
642
18
Windows Vista
For the Windows Vista firewall, the steps are somewhat different:
1. Start by opening the Control Panel and clicking the Allow a program through Windows Firewall link under the main Security link (Figure 18.18).
FIGURE 18.18 Opening the Windows Vista Allow a program through Windows Firewall Security window.
2. Alternately, you can click the Security link, and then click the Allow a program through Windows Firewall link in the Security window (Figure 18.19). 3. If you get a User Account Control warning, click Continue to proceed. 4. The Windows Firewall Settings dialog opens (Figure 18.20); it is substantially similar to the Windows Firewall dialog in Windows XP.
643
Part III
FIGURE 18.19 Clicking the Allow a program through Windows Firewall link in the Security window.
644
18
5. Click the Add program button to open the Add a Program dialog (Figure 18.21). Some SQL Server utilities are listed in the Programs list, but not SQL Server itself, or the SQL Server Browser, so you will need to browse for them. 6. First browse for the SQL Server file sqlservr.exe, which is probably located in C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn (see Figure 18.22).
FIGURE 18.22 Selecting the SQL Server executable for exclusion from the Windows Vista firewall.
645
Part III
7. After clicking Open, sqlserver.exe will be listed in the Add a Program dialog, as shown in Figure 18.23.
FIGURE 18.23 The SQL Server executable in the Add a Program dialog.
8. Click OK, and you should see sqlserver.exe in the list of Exceptions. 9. Next, browse for the SQL Server Browser service, file name sqlbrowser.exe, probably located in C:\Program Files\Microsoft SQL Server\90\Shared, and click OK to add it to the list of exceptions (Figure 18.24).
646
18
FIGURE 18.24 The SQL Server executable and browser in the list of firewall exceptions.
647
Part III
NOTE
The sample database to be upgraded is Basic Northwind.accdb, a version of Northwind with a naming convention applied to all objects, and a limited number of queries, forms, and reports.
648
18
The first screen of the Wizard (Figure 18.27; just like the one in Access 2003) offers a choice to use the existing database or create a new one. Generally, it is a good idea to create a new database.
CAUTION
The Help button on the wizard screen shown in Figure 18.27 doesnt open context-specific Help, as you might expect. Instead of getting a topic with information that would help with the decision to use the existing database or create a new one, you get the main Access Help screen, with a Table of Contents and a Search box. This is not an improvement over previous versions of Access, where context-specific Help was generally available in wizards. In Access 2003, for example, if you run the Upsizing Wizard and click the Help button on the first screen, you will get a Use the Upsizing Wizard Help topic, with pertinent information for each screen of the wizard.
649
Part III
NOTE
After clicking Next, you may be required to provide the name of your computer. To find the name of your computer, follow the steps below, depending on your Windows version:
n For Windows Vista, first select the System and Maintenance link in the Control Panel. n On the System and Maintenance page, click the See the name of this computer link under the main System link. The full computer name is located in the Computer name, domain, and workgroup settings group, as shown in Figure 18.28.
FIGURE 18.28 Finding out the full computer name in Windows Vista for use in the SQL Server connection syntax.
n For Windows XP, open the properties sheet for the My Computer icon on your desktop (or the System shortcut in the Control Panel), and click the Computer Name tab (Figure 18.29); the full computer name (with an extra terminating period) is listed in the middle of the page.
650
18
FIGURE 18.29 Finding out the full computer name in Windows XP for use in the SQL Server connection syntax.
On the second screen of the wizard, shown in Figure 18.30, select the running instance of SQL Server, using a name composed of the name of your computer followed by a backslash and SQLEXPRESS, check the Use Trusted Connection checkbox, and edit the proposed database name as desired.
CAUTION
If you are running Windows XP, most likely the default selection in the SQL Server box will be (local). Help for the Upsizing Wizard (and various documents in SQL Server Books Online) recommends using this selection, but I have found that it doesnt work; the only syntax that works on my system is DELL_DIMEN_8300\SQLEXPRESS, where the portion before the backslash is my computer name (this is the default syntax if you are running Windows Vista). I found this syntax using Google; as usual, Google has proved to be of more help in finding information on using Access 2007 than Microsofts own Help resources. However, you may find that the (local) selection works, or perhaps some other syntax, such as the name you gave SQL Server when you installed it.
651
Part III
FIGURE 18.30 Selecting the SQL Server instance to use for upsizing.
If you get the rather formidable error message shown in Figure 18.31 when clicking Next on the second screen of the Upsizing Wizard, review your SQL Server settings; you may have missed one or more of the setup steps required for Access to connect to SQL Server.
NOTE
If you get the Norton Internet Worm Protection warning (shown in Figure 18.25), click OK on it before clicking Next on the second screen of the Upsizing Wizard.
If you dont run into one or another roadblock, you will get the third screen of the Upsizing Wizard, shown in Figure 18.32, listing the tables in the Access database for selection.
652
18
I selected all the tables with data (leaving out tblBackupInfo and some lookup tables). After selecting the data tables to upsize, on the next screen (Figure 18.33) you can select various attributes to export.
653
Part III
Next, you can select to create a new Access client/server application, or link the new SQL Server tables to the existing Access front end. For this section, to convert just the tables, I chose the link option (see Figure 18.34).
FIGURE 18.34 Selecting the Link option for connecting to SQL Server.
Finally, the Upsizing Wizard success screen appears (Figure 18.35); click Finish to proceed to do the upsizing using the selected choices.
654
18
You will get a dialog, shown in Figure 18.36, with a progress bar listing each table in turn.
Finally, an Upsizing Wizard report is created and opened in Print Preview; you can print this report to get detailed information about the upsizing process, with lists of table fields for the tables that were successfully upsized, and a report on the error that prevented upsizing for any tables that could not be upsized. Figure 18.37 shows the page for the tblCategories table, listing the Access and SQL Server fields in two columns for comparison.
655
Part III
The new SQL Server database is created in the SQL Server data folder, typically C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data. The database has the .mdf extension, and there is also a matching transaction log file with the .ldf extension. The Access front end now has two sets of tables: the original (pre-upsizing) Access tables, renamed with the suffix _local, and the linked SQL server tables, as shown in Figure 18.38. The SQL Server tables are indicated by the arrow and globe icon.
FIGURE 18.38 Linked SQL Server tables in the upsized Access database.
In the Access front-end database, forms and reports should work as with local Access tables. The Orders form is shown in Figure 18.39, displaying data from the linked SQL Server tblOrders. Two of my original reports, however, wouldnt open, with Error 3219: Invalid operation. This turned out to be because the reports (or their data source queries) used the FromDate() and ToDate() functions. I made copies of the queries and reports without these functions; you can compare rptInvoices and rptInvoicesDateRange, and rptEmployeeSalesByCountry and rptEmployeeSalesByCountryDateRange, in the Basic Northwind.accdb database.
656
18
FIGURE 18.39 An Access form displaying data from a linked SQL Server table.
657
Part III
FIGURE 18.40 Selecting the client/server option for upsizing an Access database.
On the next screen, shown in Figure 18.41, you can choose whether to open the new ADP file, or keep the old Access database open.
658
18
Next, you will get a dialog with a progress bar while the database objects are upsized (Figure 18.42).
The Upsizing Wizard report opens in Print Preview; with the client/server option selected, queries and forms (and other database objects, if present) will be upsized as well as tables. The report shows which queries were upsized, and which were not, with the reasons (see Figure 18.43).
659
Part III
Forms based on tables, or queries that were successfully upsized, will look the same in the project as in the original database (see Figure 18.44).
FIGURE 18.44 A form in an Access project displaying data from a SQL Server table.
If you attempt to upsize queries as well as tables, you will find that some query types dont upsize at all, because they dont match up with views or stored procedures in SQL Server. Crosstab queries, for example, cant be upsized at all, nor can any query that uses a function in a calculated field expression. If a query isnt upsized, you need to re-create it as a SQL Server object of the appropriate type (view or stored procedure).
CROSS-REF
See the SQL Server 2005 Bible (Wiley, 2006) for more information about working with SQL Server features such as stored procedures and user-defined functions.
Figure 18.45 shows the four queries in the original Basic Northwind.mdb database that were successfully upgraded to SQL Server. The first two queries were converted to views and the third and fourth to user-defined functions, as indicated by their distinctive icons. Figure 18.46 shows the qryOrderSubtotals view in design view, and Figure 18.47 shows the qryCurrentProductList user-defined function in SQL view. You now have a client/server application consisting of an Access project front end and a SQL Server back end; from this point on you will need to use SQL Server techniques to work with the project and the back-end tables.
660
18
661
Part III
NOTE
The SQL Server database I connect to in this section is AdventureWorks, one of the sample databases you can download from the SQL Server 2005 Samples and Sample Databases (February 2007) page on the Microsoft web site, at this link: www.microsoft.com/ downloads/details.aspx?FamilyID=e719ecf7-9f46-4312-af89-6ad8702e4e6e& DisplayLang=en#filelist (or possibly a page with a later date). Make sure that any sample databases you download are compatible with Access 2007. Those on the December 2006 page are compatible with Access 2007; other sample databases posted earlier are not.
Download the AdventureWorksDB.msi file, and install it by double-clicking it. This will create the SQL Server database AdventureWorks_Data.mdf in your SQL Server data folder, usually C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Data. Next, create a new, blank Access 2007 database by selecting New from the File menu. On the dialog that opens next (Figure 18.48), select the Blank Database selection, and enter the name for the new front-end database. Close the default table (Table1) that is automatically created. To link to the SQL Server database, follow these steps:
1. Drop down the More list on the Import group on the External Data tab, and select the ODBC Database item, as shown in Figure 18.49.
662
18
663
Part III
2. On the Get External Data screen (Figure 18.50), select the Link option.
3. On the Select Data Source screen (Figure 18.51), type a name for the data source and click New to create a new DSN name.
664
18
4. Select the SQL Server driver on the Create New Data Source screen (Figure 18.52) and click Next.
5. Type a name for the DSN file on the next screen, shown in Figure 18.53, and click Next.
665
Part III
6. The Finish screen (Figure 18.54) lists the DSN name and driver you selected. (This is only the end of the DSN portion of this wizard.)
7. The next phase of the wizard starts with a screen (Figure 18.55) where you enter the description of the data source, and select a SQL Server to connect to.
666
18
8. On the Authentication screen (Figure 18.56), generally you can accept the default selection of Windows NT authentication.
9. On the next screen, depicted in Figure 18.57, check the Change the default database to checkbox, and select the SQL Server database you want to connect to.
667
Part III
TIP
If the database you want to connect to doesnt appear in the Change the default database to drop-down list, type in the database name, check the Attach database filename checkbox, and type in the full file path and name for the database .mdf file in the Attach database filename textbox.
10. The settings on the screen shown in Figure 18.58 can generally be left at their defaults.
11. Click Finish to get a screen listing the ODBC data source configuration; click Test Data Source to test the connection to the database (see Figure 18.59).
FIGURE 18.59 The final screen of the ODBC SQL Server Setup Wizard.
668
18
12. You should get the success screen depicted in Figure 18.60.
13. Click OK on the test message and then on the ODBC Microsoft SQL Server Setup screen, then click OK on the Select Data Source screen (Figure 18.61) with the newly created DSN selected.
669
Part III
14. On clicking OK, you will get the Link Tables screen, shown in Figure 18.62, where you can select the tables to link to the Access front end.
15. If one or more of the tables lacks a unique index, you will get the Select Unique Record Identifier message (Figure 18.63). Select one or more fields to create a unique index and click OK to proceed.
670
18
16. Now, as in Figure 18.64, you can see the linked SQL Server tables in the Access database window.
With the tables linked, you can proceed to create the Access front end for working with the SQL Server data.
671
Part III
Using Access as a front end to SQL Server is a topic worthy of a complete book on its own, but the Upsizing Wizard will at least help with the first steps toward creating a client-server application: moving your data from Access tables to SQL Server tables.
Summary
Because of new security features in Office and Windows, it is much more difficult to connect an Access database to a back-end SQL Server database. With the information in this chapter, you should be able to negotiate the security settings that allow you to connect an Access database to a back-end SQL Server database, allowing you to use an Access database (or project) front end to data stored in SQL Server tables.
672
SYMBOLS
' (apostrophe), 366 \ (backslash), 267, 268
241243
ExportContactsToExcel function, 191195 ExportFlatFileContactsToOutlook
A
.accda file format, 472, 473, 505, 511. See also addins, Access .accdb file format, 471, 473, 482, 511, 671 Access backing up databases, 277282 calendar pop-up, 52, 53, 320 comparing add-ins with COM add-ins, 469 comparing contact data with Outlook contact data, 359390 concatenating data for export, 143144 CopyAccessAttsToAccess procedure, 394395 CopyAccessAttsToOutlook procedure, 391392 CopyAccessContactsToOutlook procedure, 390
CopyAllOutlookContactsToAccess
procedure, 390
CopyOutlookAttsToAccess procedure,
392393
CreateProjectAppts procedure, 245247 CreateProjectTasks procedure, 253254
creating client/server applications, 657662 creating databases from templates, 225229 creating denormalized tables from linked tables, 352359 creating e-mails from tables, 8185 creating form letter reports, 810 creating worksheet-type reports, 1125 DAO support, 90, 9196
procedure, 255258 exporting appointment data to Outlook, 7278, 241243 exporting contact data to Outlook, 255258 exporting data to Excel, 184187 exporting data to Outlook items, 220233 exporting data to unformatted Excel worksheets, 5052 exporting data to Word documents, 135138 exporting data to Word using Automation code, 138182 exporting mainframe data as Outlook Journal items, 7981 exporting queries to Excel, 184187 exporting task data to Outlook, 7278, 247249 ExportTasksToOutlook function, 247249 filling Word documents with Access data by using TypeText method, 2730 Help overview, 272277 history of data transfer techniques, 4 ImportApptsFromOutlook function, 243245 ImportContactsFromOutlook function, 259262 importing Outlook appointment data from, 243245 importing Outlook contacts to, 259262 ImportTasksFromOutlook function, 250251 Layout view, 134135 methods for merging data to Word, 145182 new templates in Access 2007, 230233 Object Browser, 94
673
Index
Access (continued) placing indicators in Word templates where data should appear, 3045 preparing databases for upsizing to SQL Server, 629631 SQL Server Migration Assistant, 671672 storing data, 4 transferring timesheet data to Excel, 211218 using to enter and edit data, 4 Word Mail Merge feature, 136, 138 Word (RTF) feature, 136, 137 working with attachments, 391396 working with older format databases in Access 2007, 9196 Access Interoperability reference, 571573 Access Options dialog Add-ins page, 465, 587 Current Database page, 474, 524, 546, 630 Customize page, 518 Navigation Options button, 475, 524, 630 opening, 513514 Trust Center Page, 508 Access Projects, 657662 Access to Excel.accdb sample database, 49 Access to Outlook.accdb sample database, 72 Access to Word.accdb sample database, 27 AccessDesigner module opening, 442 Visual Basic code, 443 working with add-in events, 442443 action queries, 106 ActiveX controls, 229. See also ADO object model Add a Program dialog, 641642, 645, 646 Add Add-ins dialog, 466 Add method, 236237 AddButtons custom form Ribbon, 547549 Add-In Manager dialog, 478, 502, 509 AddInErr procedure, 441, 442, 581 add-ins, Access building, 583587 code requirements, 481482 comparing with COM add-ins, 469 construction tips, 482483
creating, 471514 customizing Ribbon with, 549560 debugging, 582583 Extras add-in code, 483499 Extras (Ribbon).accda, 549, 552 Extras 2007.accda, 471, 510514, 549 finalizing, 502503 installing, 507510, 549, 550, 551552, 583587 interpreting error messages, 506507 library database properties sheet information, 502503 location, 477 menu, 478, 549560 property builders, 480481 purpose, 472 and Registry, 472 shared, 563565 signing code, 511 storing, 472 testing, 588589 troubleshooting, 503507 types, 472473 version issues, 472, 510 and VSTO, 563565 as wizards, 472473, 479 writing code, 481483 add-ins, COM comparing with Access add-ins, 469 creating by using Visual Basic 6.0, 436467 creating LNC Control Renaming add-in, 440463 customizing, 438440 installing, 463466 selecting template, 436440 troubleshooting, 466467 using, 467468 Add-Ins menu, 510 addresses code for updating in Access, 385387 creating shipping labels in Word, 397415 labels for Mail Merge, 175176 synchronizing, 353354 as user information field, 3133
674
Index
AddToCommandBar function, 440 ADO object model code limitations, 129 Command object, 113116 Connection object, 110113 converting DAO code to, 129131 versus DAO object model, 89, 9196, 110, 130 defined, 90 equivalents of DAO objects, 130 method for working with text files, 291, 296 role of Object Browser, 94 .adp file format, 657662 AdventureWorks SQL.accdb sample database, 627 AllowMultiSelect property, 270 apostrophe ('), 366 append queries, 314, 315, 317 Application object, 139, 234235, 236 appointments, Outlook CreateProjectAppts procedure, 245247 creating based on Access table data, 245247 ExportAppointmentsToOutlook function, 241243 exporting Access table data to, 241243 exporting to Access tables, 243245 importing from Access, 7278 ArchiveData procedure, 5358 arguments, 102 attachment fields adding to Access tables, 391 data type, 297, 391 illustration, 298 loading files, 299302 overview, 297 attachment icons, 297 attachments adding by using Attachments dialog, 298299 adding by using LoadAttachments procedure, 299302 CopyAccessAttsToAccess procedure, 394395 CopyAccessAttsToOutlook procedure, 391392
CopyOutlookAttsToAccess procedure,
392393 handling in Access, 391396 handling in Outlook, 391396 PDF Products to Reorder report, 432 PDF shipping report, 428, 430 SaveAttachments procedure, 302304 saving, 302304 selecting Attachments folder path, 357359 Automation. See also Visual Basic for Applications (VBA) code for creating blank Word documents, 2728 code for creating Word documents by using bookmarks, 3639 FillWithTypeText procedure, 2830 history, 34 axes, PivotChart formatting, 419420 naming, 23
B
backing up databases to Access database folder, 277279 Extras Setup dialog options, 511512 scheduling script with Windows Vista Task Scheduler, 619625 by using BackupBackEndDB procedure, 557558 by using BackupDB function, 279282 by using BackupFrontEnd function, 489491 backslash (\), 267, 268 Backup.accdb sample database, 279, 471 BackupDB function, 279281, 282 BackupFrontEnd function, 489491 bar charts, formatting, 419423 bar codes, 152153 basExtras module, 486488 basExtrasRibbon module, 553 Basic Northwind CS.adp sample database, 627 Basic Northwind v 1 (linked SQL Server tables).accdb sample database, 627
675
Index
Basic Northwind.accdb sample database, 627, 631, 648, 656 batch optimistic lock type, 118 Beginning XML (Wiley Publishing), 525 Block reports, 14, 16 blocked content, 344345 bookmarks advantages and disadvantages, 145 inserting in Word documents and templates, 34 MergeBookmarks procedure, 165169 as method for merging Access data to Word, 145, 165169 placing in Word templates where Access data should appear, 3139 showing/hiding, 3435 bound controls and Date field, 52, 320 naming issues, 11, 16, 453454, 468, 472, 482 button XML element, 522 ButtonName property, 270
159160
cmdCreateEMails_Click procedure, 283290 cmdCreateLabels_Click procedure, 405413 cmdDeselectAll_Click procedure, 164165 cmdDocProps_Click procedure, 4345 cmdExportJobs_Click procedure
C
calculated fields, in queries, 370371 calendar, Outlook importing appointments from Access, 7278 linking folder to Access tables, 224 working with appointment data, 241247 calendar pop-up, 52, 53, 320 Callback function, 263, 264 certificates, digital creating for signing VBA code, 398402 why not to create for Access add-ins, 511 Chart templates, 423 charts. See PivotCharts, Access; PivotCharts, Excel circular references, 11 Class property, 240 client/server applications, 657662 Clipboard, 3 cmdAttachmentsFolderPath_Click procedure, 358359 cmdCancel_Click event procedure, 483484 cmdClearData_Click procedure, 315, 322, 329330
role in exporting database and spreadsheet files, 336338 role in exporting HTML and XML data, 343344 role in exporting text data, 323324 cmdInputDocsPath_Click procedure, 265266 cmdInspectJobs_Click procedure role in exporting text data, 330332 role in importing text data, 316317 cmdInspectNewJobs_Click procedure, 324 cmdLoadData_Click procedure, 294296 cmdMergetoEmailMulti_Click procedure, 348350 cmdOutputDocsPath_Click procedure, 266267 cmdReorderInventory_Click event procedure, 431432 cmdSaveJobs_Click procedure, 317, 332333 cmdSaveTimesheet_Click procedure, 207208 cmdSourceFile_Click procedure, 333334 cmdSourceTextFile_Click procedure, 318319 cmdSQL (SQL commands), 113 cmdWord_Click procedure, 154157 code. See Automation; Visual Basic for Applications (VBA) CodeDB syntax, 481, 482 Collect Data group, 220221 columns, report adjusting width, 17 default alignment, 17 COM add-in template, 436440 COM Add-ins dialog, 465466, 587 COM (Component Object Model) comparing add-ins with Access add-ins, 469 creating add-ins using Visual Basic 6.0, 436467
676
Index
creating LNC Control Renaming add-in, 440463 customizing add-ins, 438440 installing add-ins, 463466 selecting add-in template, 436440 troubleshooting add-ins, 466467 using add-ins, 467468 comboBox XML element, 522 comma-delimited text files in cmdExportJobs_Click procedure, 323 creating import specifications by using TransferText method, 306, 316 versus fixed-width text files, 315 importing, 312320 and TransferSpreadsheet method, 282 and TransferText method, 282 Command object, 113116, 130 command-bar buttons adding in code, 447448 AddToCommandBar function, 440 CreateFormCommandBarButton function, 443 CreateReportCommandBarButton function, 443 DeleteFromCommandBar function, 440 deleting, 450 RemoveAddInCommandBarButton function, 443 returning references to, 448449 role of OnConnection event procedure, 443 comments, VBA versus VBScript, 601, 603 CommonDialog control, 263, 264 Company and Contact Information form, 355357 ComplexType object, 97 Component Object Model (COM) comparing add-ins with Access add-ins, 469 creating add-ins using Visual Basic 6.0, 436467 creating LNC Control Renaming add-in, 440463 customizing add-ins, 438440 installing add-ins, 463466 selecting add-in template, 436440 troubleshooting add-ins, 466467 using add-ins, 467468
concatenation, for exporting Access data, 143144 Connect class module, modifying, 573575 Connection object, 110113, 130 Contact Documents folder, 141, 142 contact IDs, code for updating in Access, 388390 contact information comparing Outlook and Access contacts, 359390 synchronizing, 353354 Contact Information form, 353355 Contact Letter with Envelope (Bookmarks) template, 165 Contact Letters (Mail Merge) document, 176 Contact List form, 231232 Contact List (Mail Merge) document, 176 contacts, Access code for updating, 382391 copying data to Outlook, 374391 ExportContactsToExcel procedure, 191195 multiple, sending Word letters to, 158165 qryContacts query, 191 removing completely, 376 single, sending Word letters to, 150158 contacts, Outlook code for updating, 376382 CopyAccessContactsToOutlook procedure, 390
CopyAllOutlookContactsToAccess
function, 255258 exporting Access table data to, 255258 exporting to Access tables, 259262 ImportContactsFromOutlook function, 259262 ImportOutlookContacts function, 361365, 390 linking to Access tables, 224225 removing completely, 376 Container object, DAO, 98
677
Index
content controls, inserting into Word templates, 4548 ControlCA function, 456457 ControlCS function, 454456 ControlNA function, 459461 controls adding to Ribbon, 517 bound, 11, 16, 52, 320, 453454, 468, 472, 482 built-in Access Ribbon names, 521531 finding names for use in XML code, 518 grouping, 17 LNC Control Renaming COM add-in, 440463 LNCRenameFormControls function, 443, 450453 LNCRenameFormControls_Click event procedure, 443 LNCRenameReportControls function, 443, 453 LNCRenameReportControls_Click event procedure, 443 naming convention, 11, 16 renaming, 440463, 467 saving old names, 467468 ControlSO function, 457459 CopyAccessAttsToAccess procedure, 394395 CopyAccessAttsToOutlook procedure, 391392 CopyAccessContactsToOutlook procedure, 390 CopyAllOutlookContactsToAccess procedure, 390 CopyAllTemplates.vbs script, 610611 CopyFile method, 282 CopyObject method, 109, 482 CopyOutlookAttsToAccess procedure, 392393 CopyTemplateUser.vbs script, 603, 607608 CopyTemplateWorkgroup.vbs script, 603, 608610 Create Chart dialog, 424425 Create New Data Source dialog, 665 Create ObjectsSetup Setup Wizard, 584587 Create PivotTable with PivotChart dialog, 417418 CREATE TABLE statement, 109
212218
CreateField method, 109 CreateFormCommandBarButton function, 443 CreateItem method, 236 CreateItemFromTemplate object, 236 CreateNewForm procedure, 579580 CreateNewReport procedure, 580581 CreateNewTable procedure, 578579 CreateNewWorkbook function, 187188 CreateObject function
in Excel object model, 187, 188 in Outlook object model, 234, 236 when to use, 28, 39, 74, 602 in Word object model, 138, 139 CreateProjectApps function, 245247 CreateProjectTasks function, 253254 CreateReportCommandBarButton function, 443 CreateTable statement, 482 CreateTableDef method, 107, 109 CreateTextFile method, 292 CurrentDb method, 99, 481 CurrentWeekEnding function, 203, 204 cursors, ADO recordset, 117, 118, 119120 customUI XML element, 522
D
DailyDatabaseBackup.vbs script, 619625 DAO and ADO Sample Code.accdb sample database, 90 DAO object model ADO object equivalents, 130 versus ADO object model, 89, 9196, 110, 130 converting code to ADO, 129131 databases, 99 defined, 90 fields, 106109 hidden objects, 9899
678
Index
illustrated, 97 new objects, 9798 old versus new, 90, 96109 overview, 96 QueryDefs, 104106 recordsets, 99104 support in Access 2007, 90, 9196 TableDefs, 106107 when to use, 110 data sources qryOrdersAndDetails query, 62, 63 queries as, 50, 51, 52 data transfer, history of techniques, 34 Database object, 99, 130 databases. See also Access backing up by using BackupDB function, 279282 backing up by using BackupFrontEnd function, 489491 backing up to Access database folder, 277279 creating client/server applications, 657662 creating connections, 111 creating from templates, 225229 importing legacy files, 324328 naming conventions, 11 preparing for upsizing to SQL Server, 629631 sample, 230233 datasheet reports, 1119 Date field, 157 dates calendar pop-up icon, 52, 53, 320 code for creating filtered queries, 54 saving in Word documents, 157 dBASE. See legacy files DBEngine object, DAO, 98, 130 DDE (Dynamic Data Exchange), 3, 4 debugging Access add-ins, 582583 and Debug.Print statement, 601 DefaultItemType property, 240 Delete Audible Files.vbs script, 616619 DeleteFromCommandBar function, 440 denormalizing, defined, 352
Designer dialog, 439440 DialogType property, 270 digital signature creating for signing VBA code, 398402 why not to create for Access add-ins, 511 DLL files copying to default Add-ins folder, 463 creating, 463 troubleshooting COM add-ins, 466 .doc file format, 31, 146 DoCmd object CopyObject method, 109 OpenQuery method, 109 RunSQL method, 109 DocProperty field, Word versus Access Memo field, 10 placing in Word templates where Access data should appear, 3945 using field switches to format data, 152153 Document Information Panel, 148 Document object, Word, 139 document properties accessing, 146149 advantages and disadvantages, 145 creating, 149150 MergeDocProps procedure, 160, 161164 as method for merging Access data to Word, 145, 146165 overview, 3945 working around limitations, 149150 writing values to, 152 Documents collection, 139 documents, Word code for creating using bookmarks, 3637 code for creating using DocProperties field, 43 creating by using cmdWord_Click event procedure, 154157 creating by using templates, 3045 exporting Access data to, 135138 file formats, 31, 146 mail merge, creating, 176182 saving date, 157 .docx file format, 31, 146
679
Index
.dot template format, 31, 146 .dotx template format, 31, 146 drop zones, 20, 22, 23 drop-down content controls, inserting into Word templates, 4548 dropDown XML element, 522 DSN Wizard, 665669 dynamic cursors, 117, 121, 122 Dynamic Data Exchange (DDE), 3, 4 dynamic recordsets, 104 dynaset-type recordsets, 100, 101103, 130
cmdCreateEMails_Click procedure,
283290
cmdCreateLabels_Click procedure,
405413
cmdDeselectAll_Click procedure, 164165 cmdDocProps_Click procedure, 4345 cmdExportJobs_Click procedure, 323324,
336338, 343344
cmdInputDocsPath_Click procedure,
265266
cmdInspectJobs_Click procedure, 316317,
330332
E
emails
cmdCreateEMails_Click procedure,
283290 creating from Access tables, 8185 sample output, 290 Select Contacts for Email sample form, 282283 sending Access Products to Reorder report as email attachment, 432 sending Access shipping report as email attachment, 428, 430 sending text files exported from Access, 348350 synchronizing addresses, 353354 embedded macros, 232, 233 enabled XML element, 522 EOF property, 110 error handling, VBA versus VBScript, 601 error messages, add-in, 506507 Event List form, 230231 event procedures
cmdAttachmentsFolderPath_Click
348350
cmdOutputDocsPath_Click procedure,
266267
cmdReorderInventory_Click event
procedure, 431432
cmdSaveJobs_Click procedure, 317,
332333
cmdSaveTimesheet_Click procedure,
207209
cmdSourceFile_Click procedure, 333334 cmdSourceTextFile_Click procedure,
318319
Form_Load procedure, 484485 txtEmployeeID_DblClick procedure,
209211 Excel
CreateExcelTimesheets function, 211,
procedure, 358359
cmdCancel_Click procedure, 483484 cmdClearData_Click procedure, 315, 322,
329330
cmdClearTimesheet_Click procedure, 207 cmdClick procedure, 154157 cmdCreateDocuments_Click procedure,
159160
212218 creating new documents based on WSH scripts, 614, 615616 creating PivotCharts from Access queries, 415426 ExportContactsToExcel function, 191195 exporting Access data to, 184187 exporting Access data to unformatted worksheets, 5052 importing queries from Access, 415416 Object Browser, 94
680
Index
opening XML files, 346347 transferring Access timesheet date to, 211218 TransferToExcel function, 186 using templates to create formatted worksheets filled with data, 5262 workbooks versus worksheets, 183 Excel button, 184 Excel Export.accdb sample database, 187 Excel object model, 59, 187191 Excel.accdb sample database, 49 Execute method, 109 Expert One-on-One Microsoft Access Application Development (Wiley Publishing), 4, 20, 440, 480 Explorer object, 234235 Export dialog, 50, 51, 416 Export Job Data to Application File form, 336 Export Job Data to HTML or XML File form, 341, 342 Export Job Data to Text File form, 320, 321 export specifications, creating by using TransferText method, 306 ExportAccountSummary function, 195200 ExportAppointmentsToOutlook function, 241243 ExportContactsToExcel function, 191195 ExportFlatFileContactsToOutlook function, 255258 exporting Access data to Excel, 184187 Access data to legacy database files, 336338 Access data to minimally formatted Excel worksheets, 191195 Access data to Outlook items, 220233 Access data to unformatted Excel worksheets, 5052 Access table data to Outlook appointments, 241243 Access table data to Outlook contacts, 255258 Access table data to Outlook tasks, 247249 HTML files, 341347
Outlook appointment data to Access tables, 243245 Outlook contact data to Access tables, 259262 Outlook task data to Access tables, 250251 XML files, 341347 ExportNorthwindData procedure, 6269 ExportTasksToOutlook function, 247249 ExportTransactions function, 7981 ExportXML method, 342 extensions, file. See file formats External Data tab, Access 2007 Ribbon Collect Data group, 220221 customizing, 541542 for exporting Access data to Excel, 50, 184, 415 for exporting Access data to Word, 135138 for interacting with Outlook, 220221 Saved Imports button, 310 External Data.accdb sample database, 312 Extras (Ribbon).accda add-in, 549, 552 Extras 2007.accda add-in Back up Database Back End selection, 512 Back up Database selection, 512 defined, 471 Extras Options selection, 511512 List Query Fields selection, 513514 List Table Fields selection, 514 modified, 549 running menu add-ins, 510 ExtrasOptions function, 486488 ExtrasRibbon, 549, 555
F
Field object, 9496 fields in ADO object model, 94, 9596, 130 creating to store RTF data, 56 creating via NewTable procedure, 106109 in DAO object model, 92, 9498, 130 ListQueryFields add-in, 495497, 511, 513514 ListTableFields add-in, 497499, 511, 514
681
Index
fields (continued)
ListTableFields procedure, 542545
selecting for PivotCharts, 2223 selecting for PivotTables, 2021 for user information, 3133 in Word documents, 4548 file formats Access add-ins, 472, 473, 505, 511 Access Project files, 657662 Excel files, 61, 183 HTML files, 338347 Outlook files, 237 Visual Basic Script, 592 Word documents, 31, 146 Word templates, 31, 146 XML files, 338347 File Location dialog, 605 FileDialog object AllowMultiSelect property, 270 ButtonName property, 270 creating, 265267 DialogType property, 270 Filters.Add method, 270 InitialFilename property, 270 InitialView property, 270 methods, 270 overview, 263264 properties, 270 setting reference to object library, 268, 269 viewing MsoFileDialogView in Object Browser, 270 FileExists method, 617 Files and Folders.accdb sample database, 264, 302 FileSystemObject object CopyFile method, 282 FileExists method, 617 GetFile method, 608, 609 GetFolder method, 281 GetSpecialFolder method, 617 Help file, 271272 method for working with text files, 282, 290, 292, 296, 297, 298
overview, 263, 264, 271 setting reference to Scripting Runtime Library, 271 Stream object, 282 TextStream object, 282, 292 as VBS error handling alternative, 601 FillDatecontrols procedure, 204, 205 FillWithTypeText procedure, 2830 filtering reports, 1719 Filters.Add method, 270 Find* methods, 93, 101 FindFirst search method, 9394, 101 FindLast search method, 101 FindNext search method, 102 FindPrevious search method, 102 firewalls accessing SQL Server 2005 through Windows Vista, 642647 accessing SQL Server 2005 through Windows XP, 640642 fixed-width text files in cmdExportJobs_Click procedure, 323 versus comma-delimited text files, 315 creating import specifications by using TransferText method, 306311, 317 importation problems, 315 and TransferSpreadsheet method, 282 and TransferText method, 282 flat-file tables, 352359 Folder object, 235 FolderPicker dialog, 141, 268269 Folders.accdb sample database, 264 For Each...Next statement, 601 form fields, 4548 form letters, Access, creating, 810 form Ribbons, 547549 Form Wizard add-ins, 479 Format Axis dialog, 420 formatting files Access add-ins, 472, 473, 505, 511 Access Project files, 657662 Excel files, 61, 183
682
Index
HTML files, 338347 Outlook files, 237 Visual Basic Script, 592 Word documents, 31, 146 Word templates, 31, 146 XML files, 338347 Form_Load procedure, 484485 forms, Access creating emails from, 8185 Event List form, 230231 frmWeeklyTimesheet form, 202, 203211 Rich Text-enabled text blocks on, 68 sample, 230233 selecting for comparing Access and Outlook contacts, 359360 selecting from templates, 230231 Task List form, 230231 forward-only cursors, 117, 122, 127129 forward-only recordsets, 100, 103 free-form text files, 282 frmCompanyInfo form, 355357 frmContactInfo form, 353355 frmSelectOrdersForShipping form, 403, 404 frmTextImport form, 292, 293, 297 frmWeeklyTimesheet form, 202, 203211
group XML element adding to Ribbon, 517 built-in Access Ribbon names, 519520 defined, 522 removing from Ribbon, 518519 Groups object, DAO, 99
H
Help for Access 2003, 272, 276277 for Access 2007, 272274 searching, 272273 table of contents, 273274 using Google to obtain, 274275 using Object Browser, 275, 276 hidden tables, Access, making visible, 630 HideDuplicates property, 16 HTML files exporting, 341347 importing, 338341
I
ID field name, 523 id XML element, 522 idMso XML element, 522 imageMso XML element, 522 Import group, 221222 Import HTML or XML Job Data, 338, 339 Import Job Data from Application File form, 325, 329 Import Job Data from Text File form, 312, 313 import specifications creating by using TransferText method, 306311 defined, 306 on Manage Data Tasks screen, 310311 saving, 310 Import Text Wizard, 307310 ImportApptsFromOutlook function, 243245 ImportContactsFromOutlook function, 259262
G
Get External DataText file dialog, 306307 GetCustomUI function, 574 GetDefaultFolder method, 235 GetDocumentsPath function, 413414 GetFolder method, 281 GetInputDocsPath function, 267268 GetObject function in Excel object model, 187 in Outlook object model, 234, 236 when to use, 28, 74, 602 in Word object model, 138, 139 GetOutputDocsPath function, 268 GetProperty function, 500 GetSpecialFolder method, 617 Google, using to obtain Access help, 274275
683
Index
importing Access queries to Excel, 415416 Access table data from Outlook appointment data, 243245 Access table data from Outlook contact data, 259262 Access table data from Outlook tasks, 250251 HTML files, 338341 legacy database files, 324328 legacy spreadsheet files, 328335 Outlook appointment data from Access tables, 241243 Outlook contact data from Access tables, 255258 Outlook task data from Access tables, 247249 text file data, 312320 XML files, 338341 ImportOutlookContacts function, 361365, 390 ImportTasksFromOutlook function, 250251 InitialFilename property, 270 InitialView property, 270 Input Documents folder, 264, 265 Inspector object, 234235 IntelliSense, 573 Internet Explorer allowing blocked content, 344345 opening XML files, 345346 inventory, replenishing, 426432 IsLoaded function, 631 IsLoaded property, 631 Items collection, 235, 236
L
label XML element, 522 labels, naming in Report Wizard, 16 Layout view, Access, 134135 .ldf file format, 656 legacy files in cmdExportJobs_Click procedure, 337338 in cmdInspectJobs_Click procedure, 331332 in cmdSaveJobs_Click procedure, 332 DatabaseType argument values for dBASE and Paradox, 327328 importing database files, 324328 importing spreadsheet files, 328335 overview, 324 SpreadsheetType named constants for Lotus 1-2-3, 328 legends, PivotChart, 24, 419 Leszynski Naming Convention (LNC), 11, 611 letters, Access, 810 letters, Word sending to multiple Access contacts, 158165 sending to single Access contacts, 150158 library databases creating, 473481 information on properties sheet, 502503 saving changes, 506 setting reference, 503506 Link Exchange/Outlook Wizard, 223 linking Access tables to Outlook, 221229 List Table Fields command, 556, 558 ListCompany procedure, 102 ListQueryFields add-in, 495497, 511, 513514 lists, for Mail Merge, 176 ListTableFields add-in, 497499, 511, 514 ListTableFields procedure, 542545 ListValues procedure, 103 LNC Control Renaming COM add-in, 440463 LNC (Leszynski Naming Convention), 11, 611 LNC Rename Access add-in, 480 LNC Rename COM add-in, 440
J
Journal component, Outlook activating, 80 exporting Access mainframe data to, 7981
K
keyset cursors, 117, 121, 122125, 130
684
Index
450453
LNCRenameFormControls_Click event
procedure, 443
LNCRenameReportControls function, 443, 453 LNCRenameReportControls_Click event
procedure, 443
LoadAttachments function, 299302 LoadCustomUI function, 553 LoadRibbons function, 553554
lock types, ADO recordset, 118, 119120 Lotus 1-2-3. See legacy files
M
macro recorder, 202 Mail Merge method advantages and disadvantages, 146 for documents, 176182 for lists, 176 for mailing labels, 175176 MailMergeTextFile procedure, 177, 178182 as method for merging Access data to Word, 146, 175182 mailing addresses code for updating in Access, 385387 creating shipping labels in Word, 397415 labels for Mail Merge, 175176 synchronizing, 353354 as user information field, 31, 32, 33 MailMerge object, 139, 140 MailMergeTextFile procedure, 177, 178182 make-table queries, 109, 201, 205, 352 Manage Data Tasks screen, 310311 MAPIFolder object, 235 .mda file format, 472, 473 .mdf file format, 656 Memo data type, 5 Memo field, Access creating, 56 storing data in, 56 versus Word DocProperty field, 10
menu add-ins, 478, 549560 merge fields, 10 Merge It With Microsoft Office Word option, 135 MergeBookmarks procedure, 165169 MergeData.txt file, 177, 180 MergeDocProps procedure, 160, 161164 MergeTypeText procedure, 170175 merging Access data to Word bookmarks method, 145, 165169 comparison of methods, 145146 document properties method, 145, 146165 Mail Merge, 146, 175182 TypeText method, 146, 170175 message class, 240241 Microsoft Access backing up databases, 277282 calendar pop-up, 52, 53, 320 comparing add-ins with COM add-ins, 469 comparing contact data with Outlook contact data, 359390 concatenating data for export, 143144 CopyAccessAttsToAccess procedure, 394395 CopyAccessAttsToOutlook procedure, 391392 CopyAccessContactsToOutlook procedure, 390
CopyAllOutlookContactsToAccess
procedure, 390
CopyOutlookAttsToAccess procedure,
392393
CreateProjectAppts procedure, 245247 CreateProjectTasks procedure, 253254
creating client/server applications, 657662 creating databases from templates, 225229 creating denormalized tables from linked tables, 352359 creating e-mails from tables, 8185 creating form letter reports, 810 creating worksheet-type reports, 1125 DAO support, 90, 9196 ExportAccountSummary function, 195200
685
Index
241243
ExportContactsToExcel function, 191195 ExportFlatFileContactsToOutlook
procedure, 255258 exporting appointment data to Outlook, 241243 exporting contact data to Outlook, 255258 exporting data to Excel, 184187 exporting data to Outlook items, 220233 exporting data to unformatted Excel worksheets, 5052 exporting data to Word documents, 135138 exporting data to Word using Automation code, 138182 exporting mainframe data as Outlook Journal items, 7981 exporting queries to Excel, 184187 exporting task data to Outlook, 247249 exporting tasks and appointments to Outlook, 7278 ExportTasksToOutlook function, 247249 filling Word documents with Access data by using TypeText method, 2730 Help overview, 272277 history of data transfer techniques, 4 ImportApptsFromOutlook function, 243245 ImportContactsFromOutlook function, 259262 importing Outlook appointment data from, 243245 importing Outlook contacts to, 259262 ImportTasksFromOutlook function, 250251 Layout view, 134135 methods for merging data to Word, 145182 new templates in Access 2007, 230233 Object Browser, 94 placing indicators in Word templates where data should appear, 3045 preparing databases for upsizing to SQL Server, 629631
SQL Server Migration Assistant, 671672 storing data, 4 transferring timesheet data to Excel, 211218 using to enter and edit data, 4 Word Mail Merge feature, 136, 138 Word (RTF) feature, 136, 137 working with attachments, 391396 working with older format databases in Access 2007, 9196 Microsoft Database Engine (MSDE), 629, 631 Microsoft Excel CreateExcelTimesheets function, 211, 212218 creating new documents based on WSH scripts, 614, 615616 creating PivotCharts from Access queries, 415426 ExportContactsToExcel function, 191195 exporting Access data to, 184187 exporting Access data to unformatted worksheets, 5052 importing queries from Access, 415416 Object Browser, 94 opening XML files, 346347 transferring Access timesheet date to, 211218 TransferToExcel function, 186 using templates to create formatted worksheets filled with data, 5262 workbooks versus worksheets, 183 Microsoft Office Access version issues, 263264 adding .NET support to, 559573 changing installation in Windows Vista, 560562 Custom UI Editor, 518519 customization deficiency, 520521 customizing user interface, 520 FileDialog object, 263, 264270 history of data transfer techniques, 34 installing based on Windows version, 560562 version issues, 627
686
Index
Microsoft Outlook activating Journal component, 80 Application object, 234235, 236 built-in contact ID fields, 359 comparing contact data with Access contact data, 359390 CopyAccessAttsToOutlook procedure, 391392 CopyAccessContactsToOutlook procedure, 390
CopyAllOutlookContactsToAccess
procedure, 390
CopyOutlookAttsToAccess procedure,
392393
CreateProjectAppts function, 245247 CreateProjectTasks function, 253254 ExportAppointmentsToOutlook function,
241243
ExportFlatFileContactsToOutlook
function, 255258 exporting Access task and appointment data to, 7278 exporting appointment data to Access, 243245 exporting changing Access data to, 220233 exporting task data to Access, 250251 ExportTasksToOutlook function, 247249 ImportApptsFromOutlook function, 243245 ImportContactsFromOutlook function, 259262 importing appointment data from Access, 241243 importing mainframe data as Journal items, 7981 import/link folder selections, 222223 ImportOutlookContacts function, 361365, 390 ImportTasksFromOutlook function, 250251 linking Access tables to, 221229 linking Calendar folder to Access tables, 224 linking Contacts folder to Access tables, 224225
linking Tasks folder to Access tables, 223224 Object Browser, 94 referencing items in VBA code, 240241 refreshing contact data, 361374 syntax for referencing objects, 235240 working with appointments, 241247 working with attachments, 391396 working with contacts, 254262 working with tasks, 247254 Microsoft Script Editor (MSE), 592597 Microsoft Scripting Runtime Library, 263, 271 Microsoft Windows Script, 271. See also Windows Script Host (WSH) Microsoft Word creating blank documents by using Automation code, 2728 creating fancy shipping labels, 397415 creating new, blank documents based on default template, 140141 creating new, blank documents based on selected template, 141143 creating new, blank documents by using NewDoc function, 140141 creating new, blank documents by using NewDocFromTemplate function, 142143 creating new documents based on WSH scripts, 614615 Document Information Panel, 148 file format differences, 31, 146 filling documents with Access data by using TypeText method, 2730 importing Access data, 135138 importing Access data using Automation code, 138182 methods for merging Access data to, 145182 Object Browser, 94, 139 opening by using CreateObject function, 39, 4445 sending letters to multiple Access contacts, 158165
687
Index
Microsoft Word (continued) sending letters to single Access contacts, 150158 setting User Templates folder path, 604605 setting Workgroup Templates folder path, 604605 user information fields, 3133 using templates for creating formatted documents, 3045 version issues, 146 Modified Northwind.accdb sample database, 11 MSDE (Microsoft Database Engine), 629, 631 MSE (Microsoft Script Editor), 592597
O
Object Browser finding numeric equivalents of named constants, 601, 603 Folder object, 235 opening, 139 overview, 94 viewing Access object model components, 275276 object linking and embedding (OLE), 4. See also Automation object models defined, 139 Excel, 187191 Outlook, 234241 referencing VB projects to, 438439 role of Object Browser, 94 Word, 139144 Object type variables, 236 ODBC (Open Database Connectivity), 3, 4 ODBC SQL Server Setup Wizard, 666669 Office Access version issues, 263264 adding .NET support to, 559573 changing installation in Windows Vista, 560562 Custom UI Editor, 518519 customization deficiency, 520521 customizing user interface, 520 FileDialog object, 263, 264270 history of data transfer techniques, 34 installing based on Windows version, 560562 version issues, 627 Office menu. See also Access Options dialog Access Options dialog, 517, 523524, 586 opening, 523524 Properties command, 148 on Ribbon, 515 Save As submenu, 304 .oft file format, 237
N
named constants finding numeric equivalents, 601, 603 VBA versus VBScript, 601, 603 viewing in Object Browser, 164 NameSpace object, 234, 235 naming conventions, 11, 16 Navigation Options dialog, 475, 524, 630 .NET Framework, adding support to Office, 559573 .NET Programmability Support components, 561562 New Document from Template.vbs script, 614616 New keyword, 234, 236 NewDoc function, 140141 NewDocFromTemplate function, 142143 NewTable procedure, 106109 NoMatch property, 110 normalizing, defined, 348 Northwind databases, 631 Northwind Plus.accdb sample database, 397, 398, 403 Northwind 2007.accdb sample database, 90 Northwind.accdb sample database, 611, 612 Norton Internet Worm Protection warning, 648 Nz function, 601
688
Index
OLE (object linking and embedding), 4. See also Automation On Error Resume Next statement, 601, 617619 OnAction property, 443 OnConnection event procedure, 443 OnConnection method, 574 OnDisconnection event procedure, 443 OnErrorGoTo error handler, 601 Open Database Connectivity (ODBC), 3, 4 OpenDatabase method, 99 OpenQuery method, 109, 482 OpenRecordsetCommand procedure, 113116 OpenRecordsetSQL procedure, 111113 OpenSpecificWorkbook function, 189190 optimistic lock type, 118, 121 Outlook activating Journal component, 80 Application object, 234235, 236 built-in contact ID fields, 359 comparing contact data with Access contact data, 359390 CopyAccessAttsToOutlook procedure, 391392 CopyAccessContactsToOutlook procedure, 390
CopyAllOutlookContactsToAccess
procedure, 390
CopyOutlookAttsToAccess procedure,
392393
CreateProjectAppts function, 245247 CreateProjectTasks function, 253254 ExportAppointmentsToOutlook function,
241243
ExportFlatFileContactsToOutlook
259262 importing appointment data from Access, 241243 importing mainframe data as Journal items, 7981 import/link folder selections, 222223 ImportOutlookContacts function, 361365, 390 ImportTasksFromOutlook function, 250251 linking Access tables to, 221229 linking Calendar folder to Access tables, 224 linking Contacts folder to Access tables, 224225 linking Tasks folder to Access tables, 223224 Object Browser, 94 referencing items in VBA code, 240241 refreshing contact data, 361374 syntax for referencing objects, 235240 working with appointments, 241247 working with attachments, 391396 working with contacts, 254262 working with tasks, 247254 Outlook Export.accdb sample database, 230, 231 Outlook object model Explorer object, 234235 Inspector object, 234235 NameSpace object, 235 overview, 234 setting reference to, 74 Outlook Select Folder dialog, 360361 Outlook.accdb sample database, 72 Output Documents folder, 264, 265267
function, 255258 exporting Access task and appointment data to, 7278 exporting appointment data to Access, 243245 exporting changing Access data to, 220233 exporting task data to Access, 250251
P
Paradox. See legacy files Parameter object, 92 PDF files, saving to, 415, 428, 429 pessimistic lock type, 118
689
Index
phone numbers code for updating in Access, 387388 synchronizing, 353354 PivotCharts, Access assigning fields, 2223 creating, 2225 displaying legend, 24 versus Excel PivotCharts, 415, 419, 425 interactivity, 24 naming axes, 23 PivotCharts, Excel versus Access PivotCharts, 415, 419, 425 assigning titles, 421 creating from Access queries, 415426 creating from Excel worksheets, 417418 creating manually, 415423 formatting axes, 419420 layout, 419423 most popular types, 423 selecting style, 421426 viewing all types, 423 PivotTables, Access creating, 1921 interactivity, 24 PivotTables, Excel, 415, 417418 Polar chart type, 425 PostNet bar codes, 152153 PrintInvoice.vbs script, 612614 PrintReport.vbs script, 611612 procedures versus functions, 481 starting by declaring variables of different types, 236 Products to Reorder report, 430, 431432 Project Explorer, 436, 463, 503, 506, 607 Properties command, Office menu, 148 property builders, 480481
qryNorthwindAll query
creating PivotCharts from, 2225 creating PivotTables from, 1921 creating plain datasheet report from, 1119 as data source, 50, 51, 52 qryOrdersAndDetails query, 62, 63 qryThisWeeksTimesheets query, 202 queries, Access calculated fields, 370371 complex, 352 creating Excel PivotCharts from, 415426 creating PivotCharts from, 2225 creating PivotTables from, 1921 creating plain datasheet report from, 1119 as data sources, 50, 51, 52 exporting to Excel, 415416 formatting data in Excel worksheets, 6269 ListQueryFields function, 495497, 511, 513514 make-table, 109, 201, 205, 348 qryContacts query, 191 qryNeedTimesheets query, 202 qryOrdersAndDetails query, 63 qrythisWeeksTimesheets query, 202 upsizing, 660662 using as basis for exporting Access data to Excel, 191, 192 using to concatenate Access data for export, 143144 QueryDef object, 104106, 109, 113, 130 QueryDefs collection, 495, 522, 544
R
read-only lock type, 118 recordsets in ADO object model, 116129, 130 ADO versus DAO, 92, 116, 130 creating by using Command object, 113116 creating connections to external databases, 111112 cursor types, 117 cursor/lock type combinations, 121122 in DAO object model, 99103, 130
Q
qryContactIDsPhones query, 368 qryContacts query, 191 qryContactsForMerge query, 144 qryNeedTimesheets query, 202
690
Index
dynamic, 104 dynaset-type, 100, 101103 forward-only, 103 lock types, 118 overview, 116 searching, 110 snapshot-type, 103 specifying type, 99100 table-type, 100101 TestForwardReadOnly procedure, 116117 Recordset2 object, 98 references in Automation code, 138 setting for Word, 139 setting to Excel object model, 59 setting to library databases, 503506 setting to Outlook object model, 74 setting to Word Application object, 138, 139 viewing in Visual Studio Solution Explorer, 570572 refreshing contact information, 361374 Registry key information for installing Access add-ins, 472 procedures versus functions, 481 subkeys for Form Wizard add-ins, 479 subkeys for menu add-ins, 478 subkeys for property builders add-ins, 480481 Relationships diagrams, for Synchronizing Contacts database, 352353 RemoveAddInCommandBarButton function, 443 Rename Files.vbs script, 618 renaming. See also titles controls, 440463, 467 Visual Basic projects, 463, 503504 replenishing inventory, 426432 Report Wizard, creating plain worksheet reports, 1115 reports, Access cmdReorderInventory_Click event procedure, 431432 creating form letters, 810 creating Products to Reorder report, 431432
creating shipping reports, 426428, 429 creating worksheet-type, 1125 filtering, 1719 interactivity, 24 Layout view, 134135 PivotChart-type, 2225 PivotTable-type, 1921 plain worksheet-type, 1119 printing from PrintReport WSH script, 612613 Products to Reorder report, 430, 431432 selecting from templates, 233 sending Access Products to Reorder report as email attachment, 432 sending shipping report as email attachment, 428, 430 Shipping Reports and Reordering form, 426427 Upsizing Wizard report, 655657, 659 Ribbon, Access 2007 adding new control, 517 adding new group, 517 adding new tab, 517 Add-ins tab, 436, 443, 463464, 467, 469, 477 built-in control names, 521531 built-in group names, 519520 built-in tab names, 519 customizing in Access database, 519549 customizing with Access add-ins, 549560 customizing with Visual Studio 2005 shared add-in, 559589 Database Tools tab, 472, 479, 509, 554, 649 Enable Extras Tab, 552, 554 External Data tab, 50, 135138, 184, 220221, 310, 415, 541542 Extras tab, 555 form Ribbons, 547549 loading customization XML code, 547 locating add-ins, 477 new feature troubleshooting, 556 removing groups, 518519 removing tabs, 518519, 541 sources of information, 516 steps to customizing, 521
691
Index
Ribbon, Access 2007 (continued) Test Ribbon.accdb sample database, 521 USysRibbons table, 523525 writing callback procedures for command buttons, 542547 XML code for Extras (Ribbon) add-in, 550551 ribbon XML element, defined, 522 Ribbon XML file, 523, 575582 RibbonName field name, 523 RibbonXML field name, 523 RichText.accdb sample database, 5 RTF format, 5, 6, 8, 10, 136, 137 RunSQL method, 109, 482
S
Sample Code.accdb sample database, 90 sample databases Access to Outlook.accdb, 72 Access to Word.accdb, 27, 49 AdventureWorks SQL.accdb, 627 Backup.accdb, 279, 471 Basic Northwind CS.adp sample database, 627 Basic Northwind v 1 (linked SQL Server tables).accdb, 627 Basic Northwind.accdb, 627, 631, 648, 656 DAO and ADO Sample Code.accdb, 90 Excel Export.accdb, 187 Excel.accdb, 49 External Data.accdb, 312 Files and Folders.accdb, 264, 302 Folders.accdb, 264 Modified Northwind.accdb, 11 Northwind Plus.accdb, 397, 398, 403 Northwind 2007.accdb, 90 Northwind.accdb, 611, 612 Outlook Export.accdb, 230, 231 Outlook.accdb, 72 RichText.accdb, 5 Sample Code.accdb, 90 Synchronizing Contacts.accdb, 72, 352353, 357358 Tasks1.accdb, 229
Test Ribbon.accdb, 521, 542, 547 Word Export.accdb, 138, 264 Word.accdb, 27 Save to PDF utility, 415, 428, 429, 430 SaveAttachments function, 302304 SaveNo function, 501 SaveToFile method, 291 saving attachments to files, 302304 import specifications, 310 Schmid, Patrick, on Access 2007 Ribbon customizability, 520 Scripting Runtime Library, 263, 271 scripts, tools for working with. See also Visual Basic Script (VBS); Windows Script Host (WSH) Microsoft Script Editor, 592597 overview, 591 VBScript Help File, 597599 security getting around Windows Firewall to connect to SQL Server, 640647 warnings when connecting to SQL Server, 647648 warnings when installing add-ins in Access 2007 running on Windows Vista, 471472, 549, 550, 551552 Seek method, 100 Select Case statement, 316, 319, 330, 343 Select Contacts for Email sample form, 282283 Select Data Source dialog, 664, 669 Select Options dialog, 556 Select Orders for Shipping form, 403, 404 SendObject command, 220 SetProperty function, 499500 Shared Add-in template, 563 Shared Add-in Wizard, 564565, 568570 SharedCode module, 439, 440442 shipping labels adding extra information, 404 creating in Word, 397415 examples, 414
692
Index
shipping reports creating in Access, 426429 editing product amounts, 428 illustrated, 429 saving as PDF files, 415, 428, 429 sending as email attachments, 428, 430 Shipping Reports and Reordering form defined, 426 editing product amounts, 428 final shipping report, 428, 429 illustrated, 42 saving as PDF 415, 428, 429 , signature, digital creating for Access VBA code, 398402 why not to create for Access add-ins, 511 size XML element, 522 Snapshot Viewer, 135 snapshot-type recordsets, 100, 103, 130 SplitFileName function, 395 spreadsheets, importing legacy files, 324328. See also worksheets, Excel SQL commands (cmdSQL), 113 SQL Server Browser, 636, 642, 645, 646 SQL Server Configuration Manager, 633634, 637 SQL Server Express (SSE), 629 SQL Server Management Studio, 629 SQL Server Migration Assistant for Access (SSMA), 671672 SQL Server 2005 accessing through Windows XP firewall, 640642 as back-end database, 4, 483, 557558, 627628, 648, 657 books online, 632633 checking for installation, 628629 configuring for data access, 631648 configuring for remote connections, 638 connecting Office 2003 versus 2007, 627 connecting Windows XP versus Windows Vista, 627 converting Access tables to, 649657 Express version, 629
getting, 628629 linked tables, 656657 linking to table data, 662671 versus Microsoft Access, 627 Migration Assistant for Access, 671672 Network Configuration folder, 637 preparing Access databases for upsizing, 629631 restarting, 637, 639 Service Pack 2, 629 Services folder, 636 Surface Area Configuration dialog, 638639 version comparison, 629 SQL statements (strSQL), 612613 SQLEXPRESS (SQL Server) properties sheet, 635 SSE (SQL Server Express), 629 SSMA (SQL Server Migration Assistant for Access), 671672 startFromScratch attribute, 517, 522 static cursors, 117, 122, 125127, 130 Stop statement, 503 Stream object, 129, 282, 291 StripCharm function, 441 strSQL (SQL statements), 612613 styles, PivotChart, 421426 supertip XML element, 522 Surface chart type, 425 Synchronizing Contacts.accdb sample database defined, 72, 352 main menu, 357358 Relationships diagram, 352353 synchronizing data, 352353, 359
T
tab XML element adding to Ribbon, 517 built-in Access Ribbon names, 519 defined, 522 removing from Ribbon, 518519 Table object, Word, 139 TableDef object, 130, 482 TableDefs collection, 106109
693
Index
tables, Access adding attachment fields, 391 converting to SQL Server tables, 649657 creating programmatically, 107, 109 denormalized, 352359 exporting as Word RTF file, 136137 hidden, making visible, 630 linked, denormalizing, 352359 linking to Outlook data, 221229 ListTableFields function, 497499 ListTableFields procedure, 542545 loading manually, 545547 loading programmatically, 553 refreshing contact data, 361374 tables, SQL Server, linking to data, 662671 tables, Word, creating by importing from Access, 136137 Tables collection, 139 table-type recordsets, 100101, 130 Tag property, 467, 468 Task List form, 230231 Task Scheduler, Windows Vista creating tasks, 622625 Daily Database Backup.vbs script, 619620 opening, 620621 overview, 619 tasks, Outlook CreateProjectTasks function, 253254 exporting Access table data to, 247249 exporting to Access tables, 250251 ExportTasksToOutlook function, 247249 importing from Access, 7278 ImportTasksFromOutlook function, 250251 linking folder to Access tables, 223224 Tasks1.accdb sample database, 229 TCP/IP, 637 templates, Access categories of, 228 COM add-in, 436440 CopyAllTemplates.vbs script, 610611 CopyTemplateUser.vbs script, 603, 607608 CopyTemplateWorkgroup.vbs script, 603, 608610
creating databases from, 225229 New Document from Template.vbs script, 614616 new in Access 2007, 230233 User Templates folder, 603, 604605 Workgroup Templates folder, 603, 604605 templates, Excel for charts, 423 creating new worksheets from, 6263 using to create formatted worksheets filled with data, 5262 templates, Outlook CreateItemFromTemplate object, 236 saved, opening, 237238 templates, Word Contact Letter with Envelope (Bookmarks) template, 165 creating by placing bookmarks where Access data should appear, 3139 creating by placing DocProperty fields where Access data should appear, 3945 default folder, 36, 37, 4344 .dot template format, 31, 146 .dotx template format, 31, 146 file formats, 31, 146 inserting content controls into, 4548 NewDocFromTemplate function, 142143 using for creating formatted documents, 3045 Test Ribbon.accdb sample database defined, 521 form Ribbons, 547549 ListTableFields procedure, 542545 TestFileExists function, 59 TestForwardReadOnly procedure, 116117, 128129 TestKeysetOptimistic procedure, 110, 122125 TestMethodSupported procedure, 118121 TestStaticReadOnly procedure, 125127 text files ADO method, 291 comma-delimited, 282 exporting data in VBA code, 320324
694
Index
FileSystemObject method, 292, 296 fixed-width, 282, 315 free-form, 282 importing data in VBA code, 312320 methods for working with VBA code, 290, 291292, 296 reading data from, 292297 sending as email attachments, 348350 types, 282 using TransferText method, 306323 writing data to, 282292 TextStream object, 282, 292 timesheets cmdClearTimesheet_Click procedure, 207 cmdSaveTimesheet_Click procedure, 207208 CreateExcelTimesheets function, 211, 212218 engineering firm example, 202218 filling in Access forms, 202, 203211 frmWeeklyTimesheet form, 202, 203211 overview, 202 qryNeedTimesheets query, 202 qryThisWeeksTimesheets query, 202 transferring Access data to Excel, 211218 txtEmployeeID_DblClick procedure, 209211 titles. See also renaming for add-ins, 502 assigning to PivotCharts, 421 save names for worksheets, 195 TodayDate document property, 157 TransferDatabase method, 327, 330 TransferSpreadsheet method and comma-delimited text files, 282 defined, 184, 305 in ExportAccountSummary procedure, 195, 196, 201 exporting Access data to Excel, 62, 184, 186 importing Lotus 1-2-3 data to Access, 328 TransferText method, 282, 306311, 316, 317, 342 TransferToExcel function, 186
troubleshooting Access add-ins, 503507 add-ins, 506507 COM add-ins, 466467 Trust Center, 508509 trusted locations, 508509
txtEmployeeID_DblClick procedure, 209211 TypeText method
advantages and disadvantages, 146 defined, 28 FillWithTypeText procedure, 2830 MergeTypeText procedure, 170175 as method for filling Word documents with Access data, 2730 as method for merging Access data to Word, 146, 170175
U
UpdateAllAccessContacts procedure,
382391, 390
UpdateAllOutlookContacts procedure,
376382, 390 Upgrade Report, 562563 Upgrade Wizard, 562 Upsizing Wizard Access 2003 versus Access 2007, 627 converting Access tables to SQL Server tables, 649655 creating client/server applications, 658659 preparing Access databases for upsizing to SQL Server, 629631 versus SQL Server Migration Assistant for Access, 671672 user addresses, 31, 32, 33 user information fields, 3133 user names, 31, 33 User Templates folder copying templates to, 603 setting path, 604605 viewing path, 604605 Users object, DAO, 99
695
Index
USysRegInfo system table creating, 473481 defined, 472 fields, 476477 subkeys for Form Wizard add-ins, 479 subkeys for menu add-ins, 478 subkeys for property builder add-ins, 480481 USysRibbons table, 523525
ControlCA function, 456457 ControlCS function, 454456 ControlNA function, 459461 ControlSO function, 457459 CreateCTT function, 461463 CreateFormCommandBarButton function,
443
CreateReportCommandBarButton function,
V
variables data typing, 601 naming convention, 11 Object type, 236 VBA versus VBScript, 601 VB 2005 Express, 562 VB 2005 XML Editor, 518519, 525, 526 VBA. See Visual Basic for Applications .vbs file format, 592 VBS (Visual Basic Script) CopyAllTemplates.vbs script, 610611 CopyTemplateUser.vbs script, 603, 607608 CopyTemplateWorkgroup.vbs script, 603, 608610 DailyDatabaseBackup.vbs script, 619625 Delete Audible Files.vbs script, 616619 Help file, 597599 and Microsoft Script Editor, 592596 New Document from Template.vbs script, 614616 PrintInvoice.vbs script, 612614 PrintReport.vbs script, 611612 Rename Files.vbs script, 618 scheduling backup script, 619625 versus Visual Basic for Applications, 599603 visible XML element, 522 Visual Basic. See also Visual Basic for Applications (VBA) AccessDesigner module, 444463 AddInErr procedure, 441, 442 AddToCommandBar function, 440 command-bar buttons, 447450
443 creating COM add-ins by using VB 6.0, 436467 DeleteFromCommandBar function, 440 LNCRenameFormControls function, 443, 450453 LNCRenameFormControls_Click event procedure, 443 LNCRenameReportControls function, 443, 453 LNCRenameReportControls_Click event procedure, 443 naming projects, 463, 503504 OnConnection event procedure, 443 OnDisconnection event procedure, 443 referencing projects to object libraries by using VB 6.0, 438439 RemoveAddInCommandBarButton function, 443 SharedCode module, 439, 440442 StripCharm function, 441 using legacy code for working with text files, 292, 296 Visual Basic for Applications (VBA) Access add-in code requirements, 481482 AddInErr procedure, 581 ADO object model versus DAO object model, 89, 9196, 110, 130 ArchiveData procedure, 5358 argument styles, 102 BackupDB function, 279281, 282 BackupFrontEnd function, 489491 Callback function, 263, 264 callback procedures for Ribbon command buttons, 542547
696
Index
cmdAttachmentsFolderPath_Click
CopyOutlookAttsToAccess procedure,
procedure, 358359 cmdCancel_Click event procedure, 483484 cmdClearData_Click procedure, 315, 322, 329330 cmdClearTimesheet_Click procedure, 207 cmdCreateDocuments_Click procedure, 159160 cmdCreateEMails_Click procedure, 283290 cmdCreateLabels_Click procedure, 405413 cmdDeselectAll_Click procedure, 164165 cmdDocProps_Click procedure, 4345 cmdExportJobs_Click procedure, 323324, 336338, 343344 cmdInputDocsPath_Click procedure, 265266 cmdInspectJobs_Click procedure, 316317, 330332 cmdInspectNewJobs_Click procedure, 324 cmdLoadData_Click procedure, 294296 cmdMergetoEmailMulti_Click procedure, 348350 cmdOutputDocsPath_Click procedure, 266267 cmdSaveJobs_Click procedure, 317, 332333 cmdSaveTimesheet_Click procedure, 207208 cmdSourceFile_Click procedure, 333334 cmdSourceTextFile_Click procedure, 318319 cmdWord_Click procedure, 154157 CopyAccessAttsToAccess procedure, 394395 CopyAccessAttsToOutlook procedure, 391392 CopyAccessContactsToOutlook procedure, 390
CopyAllOutlookContactsToAccess
392393
CreateAndTestQuery function, 5859,
104106
CreateDenormalizedContactsTable
212218
CreateNewForm procedure, 579580 CreateNewReport procedure, 580581 CreateNewTable procedure, 578579 CreateNewWorkbook function, 187188 CreateObject function, 28, 39, 74, 138, 139,
creating digital signature for signing code, 398402 CurrentWeekEnding function, 203, 204 ExportAccountSummary function, 195200 ExportAppointmentsToOutlook function, 241243 ExportContactsToExcel function, 191195
ExportFlatFileContactsToOutlook
function, 255258
ExportNorthwindData procedure, 6269 ExportTasksToOutlook function, 247249 ExportTransactions function, 7981
procedure, 390
Extras add-in code, 483499 ExtrasOptions function, 486488 FillDatecontrols procedure, 204, 205 FillWithTypeText procedure, 2830 formatting Excel worksheets, 6269 Form_Load procedure, 484485 GetCustomUI function, 574 GetDocumentsPath function, 413414 GetInputDocsPath function, 267268 GetObject function, 28, 74, 138, 139, 187, 234, 236, 602 GetOutputDocsPath function, 268 GetProperty function, 500 history, 4
697
Index
Visual Basic for Applications (VBA) (continued) ImportApptsFromOutlook function, 243245 ImportContactsFromOutlook function, 259262 ImportOutlookContacts function, 361365, 390 ImportTasksFromOutlook function, 250251 IsLoaded function, 631 ListCompany procedure, 102 ListTableFields procedure, 542545 ListValues procedure, 103 LoadAttachments function, 299302 LoadCustomUI function, 553 LoadRibbons function, 553554 MailMergeTextFile procedure, 177, 178182 MergeBookmarks procedure, 165169 MergeDocProps procedure, 160, 161164 MergeTypeText procedure, 170175 naming projects, 463, 503504 NewDoc function, 140141 NewDocFromTemplate function, 142143 NewTable procedure, 106109 Nz function, 601 OpenRecordsetCommand procedure, 113116 OpenRecordsetSQL procedure, 111113 OpenSpecificWorkbook function, 189190 SaveAttachments function, 302304 SaveNo function, 501 SetProperty function, 499500 SplitFileName function, 395 TestFileExists function, 59 TestForwardReadOnly procedure, 116117, 128129 TestKeysetOptimistic procedure, 110, 122125 TestMethodSupported procedure, 118121 TestStaticReadOnly procedure, 125127 TransferToExcel function, 186 txtEmployeeID_DblClick procedure, 209211 UpdateAllAccessContacts procedure, 382391, 390
UpdateAllOutlookContacts procedure,
376382, 390 versus VBScript code, 599603 Visual Basic Script (VBS) CopyAllTemplates.vbs script, 610611 CopyTemplateUser.vbs script, 603, 607608 CopyTemplateWorkgroup.vbs script, 603, 608610 DailyDatabaseBackup.vbs script, 619625 Delete Audible Files.vbs script, 616619 Help file, 597599 and Microsoft Script Editor, 592596 New Document from Template.vbs script, 614616 PrintInvoice.vbs script, 612614 PrintReport.vbs script, 611612 Rename Files.vbs script, 618 scheduling backup script, 619625 versus Visual Basic for Applications, 599603 Visual Studio customizing Access Ribbon with shared add-in, 559589 running in Windows Vista, 565567 Shared Add-in template, 563 viewing references in Solution Explorer, 570572 XML Editor, 525, 526 Visual Studio Tools for Office 2005 (VSTO), 563565
W
Windows Firewall Settings dialog, 643644, 647 Windows operating system accessing SQL Server 2005 through firewall, 640647 history of data transfer techniques, 34 version issues, 627628 Windows Script Host (WSH) Microsoft Script Editor, 592606 office scripts, 611616 overview, 591 setup scripts, 603611
698
Index
tools for scripts, 591599 useful scripts, 603619 VBScript Help File, 597599 Windows Vista Access add-in installation issues, 471472, 508, 549, 550, 551552 accessing SQL Server through firewall, 643647 adjusting security settings, 471472, 549550 Explorer, 146, 149 running Visual Studio 2005 in, 565567 Task Scheduler, 619625 Windows XP, accessing SQL Server 2005 through firewall, 640642 WithEvents statement, 443 wizards Access add-ins as, 472473, 479 construction tips, 482483 Create ObjectsSetup Setup Wizard, 584587 DSN Wizard, 665669 Form Wizard add-ins, 479 Import Text Wizard, 307310 Link Exchange/Outlook Wizard, 223 ODBC SQL Server Setup Wizard, 666669 Report Wizard, 1115 Shared Add-in Wizard, 564565, 568570 Task Scheduler, 622625 Upgrade Wizard, 562 Word creating blank documents by using Automation code, 2728 creating fancy shipping labels, 397415 creating new, blank documents based on default template, 140141 creating new, blank documents based on selected template, 141143 creating new, blank documents by using NewDoc function, 140141 creating new, blank documents by using NewDocFromTemplate function, 142143 creating new documents based on WSH scripts, 614615 Document Information Panel, 148
file format differences, 31, 146 filling documents with Access data by using TypeText method, 2730 importing Access data, 135138 importing Access data using Automation code, 138182 methods for merging Access data to, 145182 Object Browser, 94, 139 opening by using CreateObject function, 39, 4445 sending letters to multiple Access contacts, 158165 sending letters to single Access contacts, 150158 setting User Templates folder path, 604605 setting Workgroup Templates folder path, 604605 user information fields, 3133 using templates for creating formatted documents, 3045 version issues, 146 Word Catalog-type merge, 176 Word Export.accdb sample database, 138, 264 Word Mail Merge feature, Access, 136, 138 Word object model, 139144 Word Options dialog, 604605 Word properties sheet Custom tab, 149 General tab, 148 Summary tab, 149, 150 Word (RTF) feature, Access, 136, 137 Word.accdb sample database, 27 workbooks versus worksheets, 183 Workgroup Templates folder copying templates to, 603 setting path, 604605 viewing path, 604605 worksheets, Excel code for creating from template, 5455 code for saving and closing, 5657 code for writing data, 5556 creating from template in VBA code, 6269 creating PivotCharts from, 417418
699
Index
worksheets, Excel (continued) formatted, using templates to create, 5262 formatting in VBA code, 6269 minimally formatted, creating, 191195 simple, tabular, creating, 191195 unformatted, exporting Access data to, 5052 ways of creating, 62 versus workbooks, 183 worksheets, legacy, importing, 324328 worksheet-type reports, Access, 1119 Workspace object, 99, 130 WriteText method, 291 WSH (Windows Script Host) Microsoft Script Editor, 592606 office scripts, 611616 overview, 591 setup scripts, 603611 tools for scripts, 591599 useful scripts, 603619 VBScript Help File, 597599
X
XCopy feature, 629 .xls file format, 61, 183 .xlsx file format, 61, 183 XML files. See also HTML files and Access, 338 code for AddButtons Ribbon, 547548 code for ExtrasRibbon, 550551 color-coding for code, 516, 525, 526 creating and editing code, 517519 creating code, 522542 exporting, 341347 importing, 338341 opening in Excel, 346347 opening in Internet Explorer, 345346 XML Notepad 2007, 517, 525
Z
ZipCode DocProperty field, 152153
700
Office heaven.
Get the first and last word on Microsoft Office 2007 with our comprehensive Bibles and expert authors. These are the books you need to succeed!
978-0-470-04691-3
978-0-470-04403-2
978-0-470-04689-0
978-0-470-04368-4
978-0-470-04702-6
978-0-470-04645-6
978-0-470-04673-9
978-0-470-00861-4
Wiley and the Wiley logo are registered trademarks of John Wiley & Sons, Inc. and/or its affiliates. All other trademarks are the property of their respective owners.