Updated: March 15, 1996 |
Phone: (602) 577-2661
Fax: (602) 529-7608
CompuServe: 73671,55
Internet: 73671.55@Compuserve.com
Introduction
Electronic Forms Development Overview
Getting Started: Create a New Project
The Sample Application: A Shipping Request
Test The Application
Where to Go From Here
Workgroup Applications are fast becoming one of the most popular and demanding of development environments. The potential benefits come with a meaningful automation of many tasks in today's office environment. Workgroup Applications, by definition, force a whole new way of working together by electronic means. With this new way of thinking for the user, comes a new way of thinking for the developer. If we are to develop applications to take advantage of the new technologies, we must be able to understand how these technologies are applicable to the user and their challenges.
The target audience for this paper is the Microsoft® Windows® operating system development professional. In that light, this paper assumes a good working knowledge of Microsoft's Visual Basic® programming system. In addition, familiarity with the Microsoft Mail 3.x client programs is helpful.
The actual definition of what makes up a "Workgroup Application" is as varied as the applications themselves. Simply put, Workgroup Applications enable two or more people to work together to accomplish a common goal or function.
There are several examples of these types of applications. Some of the more common ones are listed below.
Information routing applications include the functionality of document routing slips, information that needs to be sent from one individual to another, and so forth. You might think of information routing as the typical magazine routing slip that probably runs through your organization.
This is the type of application we'll be covering here.
This type of application is similar to routing except the primary focus of information sharing is to get information to people. This can also be referred to as a "broadcast" type message.
An example of this might be sending out formatted lists, reports, and so forth. Please keep in mind, however, that with these types of items, they should be items that require your application to be useful to the recipient. Don't reinvent the wheel and send out tabular data that could just as easily have been sent in a Microsoft Excel spreadsheet attached to a standard mail message.
This type of application allows several people to contribute to a final product. For example, if several departments are preparing information for the budget, you may want to have an application format and send last year's budget, along with some backup information, to each department manager. The department managers review this information, prepare their new budgets, and submit them back to you. Each of these budgets gets automatically (after you press the OK button) consolidated and inserted into your overall budget spreadsheet.
Workgroup applications use several different features of your system to accomplish the information sharing that is needed. First, they often use communications between systems. These communications range from real-time dynamic data exchange (DDE) communications between workstations to "Real Enough Time" (electronic mail backbone) communications.
The theory is that you should use the existing infrastructure as much as possible to get your application up and running. As an example, if you have already implemented Microsoft Mail, wouldn't it make sense to use the existing mail system to move information around your network?
Think of your existing systems as the Electronic Backbone that will be used to support your system. Using messaging API (MAPI), you'll be able to insulate your applications from the work of moving information around, focusing instead on the application and processing of that information. Your application won't even need to know if the users of the program are local, remote, across a gateway, or a mixture of these. Let the existing systems do the work for you.
A goal of your workgroup application should be to use the existing security infrastructure wherever possible as well. Much the same as the "electronic backbone" described above, the security system already in place offers many benefits.
Users should not, unless circumstances deem it absolutely necessary, be forced to sign in to multiple systems. If you don't add any security, you'll be assured of an already validated user when they begin using your system. To begin using any system based on this architecture, the operating system (Microsoft Windows, Microsoft Windows NT, and so forth) will force the user to sign on, and it will validate him with any appropriate passwords. Otherwise, the user won't even be able to get to your application.
Use the existing security systems to your advantage. Don't force the user to re-enter a user name and password. Get this information from the system and from MAPI APIs as you need it if at all possible.
The Electronic Forms Designer is a layer between your application and Visual Basic. Visual Basic is a core component of any Electronic Forms Designer application, as is the MAPI support, Open database Connectivity (ODBC) if you have a database, and the central login and security systems if you use those options.
Layers of the Application
In this first section, we'll be covering how you work with the Electronic Forms Designer in it's starting state. You'll need to be familiar with compose and read forms, message classes, and several other items before moving on to the sample application.
There are several points to be considered before the actual development begins:
The development process for workgroup applications under Microsoft Mail encompasses several different steps. These include:
There are several aspects to setting up a Visual Basic application that will be using electronic forms that are different from a standard Visual Basic application. First, the application will be tying in to existing systems (MAIL, backbone protocols, security, and so forth) and will be using some existing configuration files. For example, your application will be relying on the MSMAIL.INI file and the SHARED.INI file for configuration information.
In addition, dynamic-link libraries (DLL) are required on the user's system that are specific to Electronic Forms Designer applications.
Here's the general flow of information through the Electronic Forms Designer application cycle:
Eforms Application Flow at the System Level
There are several different aspects of the MSMAIL.INI file that relate to your application. These different settings establish the message type, how a message is processed, and access through menus to the application, if applicable. In an application where you will have multiple people accessing the application, it may make sense to implement it in a shared fashion, making administration, updates and installation simpler.
If you are going to install an application that will be accessed by multiple users, consider implementing a SHARED.INI settings file for those applications. Functionally, the settings are no different from those in a local installation. The difference comes when you install the application in a central location and the users have access to it automatically the next time they start their mail systems.
In the user's local MSMAIL.INI file, setting the SharedExtensionsDir option in the [Microsoft Mail] section will give the user access to the SHARED.INI file. The format of the setting is as follows:
SharedExtensionsDir=drive:\eformdir_path
For example:
SharedExtensionsDir=M:\SHARED
For this format to work, there must already be a valid network share to the resource containing the shared directory.
If UNC (Universal Naming Convention) names are supported by your network operating system, you can specify the location of the file in that format:
SharedExtensionsDir=\\ServerName\ShareName
For example:
SharedExtensionsDir=\\SERVER1\EFORMS
This value is then available to your custom message type declarations by including <ExtsDir> in the message type command line. For example, if the executable is located in subdirectory named "Eforms" under the shared directory, you would specify:
<ExtsDir>EFORMS\APPNAME.EXE.
Your application should read the user's MSMAIL.INI file and retrieve the shared directory as a root location for any shared databases. This is done with the Microsoft Windows API "GetPrivateProfileString" function call. This function returns a value from the INI file associated with the section and key you specify. In this way, your implementation on the user's system will be using the same standards as other existing workgroup applications.
Once you have established the SHARED.INI file, you can add the same sections to it that you would find in the user's local MSMAIL.INI file. The entries are then added to the users' system as appropriate when their mail client package loads.
You can include the [Custom Messages] section in the SHARED.INI file.
Each of these custom messages will become a part of the users' system the next time they start the mail client software.
The message type entry is how MSMAIL knows how to react to your message when it arrives in the inbox. The [Custom Messages] section contains the various message types that are supported by your mail system. The format of the message type line is:
MessageClassName=Mail version;;[command name];;DLL name;[<ExtsDir>command string];operation map; [status line];;;
The actual value for the message class comes from your application. In the EFORM.BAS module, you must establish the Message Class, and it must match the value entered here. Note that case is significant; it must match exactly. For example:
IPM.TechED.Shipping=3.0;;Shipping Request Form;; _MEFLIB.DLL;<MC:IPM.TechED.Shipping>C:\windows\shipping.exe _-MSG <COMMAND> <MESSAGEID>;1111111000000000;Shipping Request _Form (Tech*Ed '95);;;
This indicates exactly how our application is accessed from within mail. In the example, the application is implemented as a local application. We've therefore specified the path to the application relative to our local system ("C:\WINDOWS..."). In a shared installation, this would be as follows:
IPM.TechED.Shipping=3.0;;Shipping Request Form;; <ExtsDir>MEFLIB.DLL;<MC:IPM.TechED.Shipping> <ExtsDir>shipping.exe -MSG <COMMAND> <MESSAGEID>;1111111000000000;Shipping Request Form (Tech*Ed '95);;;
Using this technique specifies that the DLL to load the program, MEFLIB.DLL, is located in our shared directory, as is the actual executable used to run the application. Note that you must have established the SharedExtensionsDir as outlined above before this will work correctly. In addition, make sure you put the message type in the [Custom Messages] section of SHARED.INI -- you may have to create this section if the SHARED.INI file is new or not fully used at the present time.
The "1" and "0" entries control the actual operations that are supported for the form. These are outlined in detail in the Microsoft Electronic Forms Designer Developer's Guide, chapter 9, pp. 134 and 135.
If you want to make your application appear on a menu, you have two choices. You can add it to a standard existing menu, or you can add a custom menu for your application.
To add the option to a menu, locate the [Custom Commands] section of the MSMAIL.INI file. In that section, you can add an entry as follows:
Menu Item Name=3.0;Menu Name;Menu Option;Option Location; Command Line; Command Line parameters; Description;;;
For Example:
Shipping=3.0;eforms;&Shipping Request;2; C:\WINDOWS\SYSTEM\MEFLIB.DLL;c:\windows\shipping.exe; 0000000000000000;Initiate shipping request;;;
This will also work for menus you specifically want to add to the menu bar. To do this, locate the [Custom Menus] section of the MSMAIL.INI file. In that section, you can add an entry as follows:
Menu Name=3.0;Menu Title;Window;Description
For example:
Eforms=3.0;EF&orms;Window;Select from electronic forms, settings and options.
You can then refer to the Menu Name in your Custom Commands, placing options in the menu.
When you get ready to use the Electronic Forms Designer package, you'll first need to create a new project from the template. Create a new subdirectory and copy the TEMPLATE subdirectory to your new subdirectory. This will copy the basic files necessary to begin work on your project. The files copied are the basic compose and read forms, supporting files and icons, and a series of libraries. Also, a reference is set up to the library of additional electronic forms routines.
If, as you load a project, you get several "file not found" messages, there is a quick remedy. First of all, this usually means that you've installed this project in a different subdirectory structure than what was originally specified when you installed Electronic Forms Designer.
Look for the subdirectory, usually "...\EFORMS\LIB," containing the following files:
Add each of these files to your project and you should be set to go. These files MUST be present for the Electronic Forms Designer to function correctly. These files contain the libraries and variables that are needed for the Electronic Forms Designer to process messages. These files are not generally modified during the development of an application, and they are marked read-only with the installation of the Electronic Forms Designer.
The basic electronic form ("eform") application consists of two different forms: Compose and Read. The compose form is what the user sees when they create a message using your application. The Read form is what is displayed to a user receiving and reading the form.
The basic components of a mail message that you're used to working with are all provided, including the destination of the message, the ability to have "CC" addresses, and the standard buttons that allow you to send the message to the recipients you've designated.
Behind this message are quite a few time-saving features. Specifically, there is access to the standard MS Mail address book, functionality to Send the message, check the recipient list, resolve any non-unique names, and so forth. It's all there, in the different procedures, functions, and libraries referenced in the buttons, menus, and controls.
There are a couple of things to understand about how this form works as you begin working with it.
First, the form is laid out so that all the controls you will be working with, at least those that will be transported to the recipient, are placed within a frame, specifically the "PnlCanvas" control. In the previous example, you'll notice that there is a line just above the bottom of the form. That is the actual line of the "canvas" on which all items must be placed if they are to be sent as part of the application.
Second, you don't necessarily have to use all of the different controls that are presented here. In fact, if you have a pre-routed form, like we'll be doing here, you'll want to make both the "lstToRecipients" and the "lstCCRecipients" controls disappear on the form that the user will actually see. A quick note: Save yourself some trouble and just turn off the Visible property for these controls, don't actually remove them. Also, make sure you disable that property for both the text caption for each field and the frame that the field was placed into to give it that 3-D look.
Third, you can change the format or appearance of any control and it will still work correctly.
Note While you're exploring the form, make sure you inspect the "Tag" property for each control. Notice that it's set to some reasonably logical value on all items that you'd expect to be seen by the recipient. This is important.
Now, with that in mind, the Read form works much the same. Again, as with the Compose form, notice that there is a "canvas" that contains all the objects that are transferred between the Compose and Read forms. Also, if you noticed the Tag property on the controls in the Read form, you'll also notice that the corresponding controls on the Read form have the same Tag properties. This is vital to the successful functioning of your application using the Electronic Forms Designer library.
When the Electronic Forms Designer processes your message to send it, it will inspect all the controls on the canvas, and, if they have a tag value, they are bundled up and sent with the message. If there is no tag value, they are not sent.
When the Electronic Forms Designer un-packages your message on the other end, it'll again inspect the tag property and use it to identify where the information should go on the Read form.
Important The failure to put a tag value on a given control will prevent information from transferring as you'd expect between the Read and Compose forms.
Also, as you're working with the basic template, adding controls, moving things around, make sure that you are working within the canvas. Specifically, if you paste a control to a form and the control "disappears," you know that you forgot to select the canvas prior to pasting the new control. A way to correct this situation is:
1. Use the property box and select the drop down list box at the top of the window.
2. Select the control you want to correct.
3. Delete the control.
4. Click on the canvas.
5. Re-add the control.
Adding controls to the canvas is identical to putting controls within a frame; you must first select the frame (canvas) and then place the controls.
Now that you've established your project by setting up the fundamental forms, you're ready to begin working on your specific application.
One of the first things you'll need to do is to establish a message type for the application. The message type dictates to the mail system what executable will be used to launch the electronic form.
To define the message type, select the EFORM.BAS module and look for the following line:
Global Const MESSAGE_CLASS = "IPM.Microsoft.Template"
This line specifies the message type. There are two types of message types currently supported by MS Mail. IPM messages, as in this example, are Inter-Personal Messages, or messages generally read by you and me and intended for transportation between two mail client front-end packages. Generally, your messages will be of this type.
The second type of message is the IPC message, or Inter-Process Communication. These messages do not show up in your inbox and are expected to be processed by the recipient, probably by some process expecting the message and its contents. These message types give you the ability to transport data between systems without requiring the intervention of the user.
As I mentioned above, generally you'll be using the IPM type, so leave the prefix at IPM. You should set the middle portion to some descriptive heading; for our example, we've put "TechED" in the center portion. This is arbitrary, just be sure to use it for organization of your messages. For example, if you have several different applications for different areas of the company, it might be helpful to use this portion to determine what department the form is used to automate.
The last portion defines specifically what the form is. For our example, we've placed "Shipping" as the final portion, as we're building a shipping request.
Therefore, our message type line is as follows:
Global Const MESSAGE_CLASS = "IPM.TechED.Shipping"
Next, you'll need to set up mail to recognize the form you'll be creating. Some people will do this a little later in the cycle, but I do it here so that I don't forget and get aggravated during the testing cycle.
Edit the MSMAIL.INI file, usually located in the \WINDOWS subdirectory. Find the [Custom Messages] section and locate the line that reads:
;IPM.VendorName.Sample=3.0;;Sample Form;;C:\WINDOWS\SYSTEM\MEFLIB.DLL;<MC:IPM.VendorName.Sample> C:\VB\EFORMS\PROJECTS\SAMPLE\SAMPLE.EXE -MSG <COMMAND> <MESSAGEID>;1111111000000000;Sample Form;;;
This line already has the items you need to set up your form, so copy it and insert it in a line just below and remove the semicolon. Change it to read as follows:
IPM.TechED.Shipping=3.0;;Shipping Request Form;;MEFLIB.DLL;<MC:IPM.TechED.Shipping>C:\windows\shipping.exe -MSG <COMMAND> <MESSAGEID>;1111111000000000;Shipping Request Form (Tech*Ed '95);;;
There are several different things to know about this new line. First we've changed the message type to match our declaration EFORM.BAS. Remember, with the message type, case (upper and lower) is significant. Next, a brief description of the form is given. The command line to execute the program is next.
Note that "MEFLIB.DLL" is a DLL provided with Electronic Forms Designer that allows you to execute your program and is passed as a parameter. Mail doesn't have a way to launch an executable, only DLL's, so this is the way to get your program started. You may need to specify the full path to MEFLIB.DLL, usually \WINDOWS\SYSTEM\MEFLIB.DLL.
Between MEFLIB.DLL and the path to your program, the message type is again shown. Make sure you update this to match your program. Again, remember, case is significant. The last thing to worry about here is the final description for your application.
Don't change the other items (in brackets, 1's and 0's) at this time. These items control the information passed to your application and different aspects relating to when the application is actually loaded.
Save your changes to the MSMAIL.INI and re-start your mail client package. If you receive any errors as it comes up, check your typing.
A quick note here for your debugging efforts, if you're attempting to read the form, but can't seem to get Mail to recognize it, check the case on your MESSAGE_CLASS defined here and compare it to your MSMAIL.INI file (more on that later). Case IS significant when integrating your message with the mail system.
You'll know if mail doesn't recognize the message type, because when you double click on the message, mail will pull up the standard read form and indicate that it can't locate the application to process the message correctly.
When you've got all of this set up, the next step is generally creating the database behind the application, if you need one, and then creating the actual forms that will be used in the application. I recommend that you create the database ahead of time so you'll know column, table, and database names as you code your application. Also, if you create the database items first, you'll be able to use the various facilities of the bound controls within Visual Basic, such as letting it determine how to choose the columns you want, and so forth.
In some cases if you add a new control to your form, you'll have to set up two different procedures to package and un-package your new control. As an example, this would be the case with a masked edit control.
You have to tell the ReadTaggedControls and WriteTaggedControls procedures how to handle the new control. In this case, it's pretty straightforward. In the EFORM.BAS module, locate these routines and find the TEXTBOX statements. They should be something like this:
... ElseIf TypeOf ctrl Is TextBox Then MEFWriteText package, Tag, CStr(ctrl.Text) ...
Copy this code and add it to the bottom of the loop, after the comment that indicates where to add custom controls. Change the "TypeOf ctrl Is TextBox" to "MaskEdit." and the form will now correctly read and write the control. Remember to do this in both the Read... and Write... routines.
Remember, the only way the routines know what to package up or un-package, is by examining the Tag property for a given control. Make sure you set these values in your Compose and Read forms.
What we will do in this exercise is to create a workgroup application that uses the routing concept and guides you through the design, basic coding, and implementation of the application.
The sample application we'll be working with is a shipping request form. This application has the following general system flow:
Shipping Request System Flow
Our shipping request allows the originating operator to create a new message detailing the user's shipping desires. Based on the type of shipment requested, the request is automatically routed to either the shipping coordinator directly, or to the approval supervisor for the requester.
Our shipping request has several design points that must be met.
Within Visual Basic, if you choose the Window command, you'll be able to call up the Data Manager directly. Alternatively, you can create your database with ACCESS. In our examples here, we'll use the data manager.
First, create the database. Choose File, New, and specify an Access (latest revision) database. Give it any name, but in our case, it's called TECHED.MDB.
Next, you'll need to create the tables for your application. Here's what we have in our sample application:
Approval Database Table
Column Type Field Size UserID Text 50 ApprovalID Text 50 ShippingID Text 50
The approval table will actually dictate the routing for the form. The user ID corresponds to the user that is creating, or originating the shipping request. We first validate the user using his sign-in ID to lookup USERID in this table. The corresponding values for ApprovalID and ShippingID are then used in the application to set up the routing.
In cases where ApprovalID = UserID, the user is allowed to "authorize" his own shipments and the request is forwarded directly to ShippingID for processing. If the ApprovalID does not match the UserID, and the shipment requires approval, the request will first be routed to the ApprovalID user, then it will either be declined and returned to the originator, or approved and forwarded to ShippingID.
PackageID Database Table
Column Type Field Size ID Double N/A
The package ID is simply a counter that will return the last used PackageID. We add one to this value and save it back to the database, using the new value as our package ID.
Shipments Database Table
Column Type Field Size Address1 Text 100 Address2 Text 100 City Text 40 State Text 2 Zipcode Text 10 Requestor Text 50 ShippedBy Text 50 DateShipped Text 8 TimeShipped Text 10 DateOrigRequest Text 8 ShipMethod Text 50 ApproveBy Text 50 BillingRef Text 100 Comments Memo N/A
The Shipments table will track the actual packages that get approved and shipped. Once the shipment is completed by the ShippingID operator, the information about the shipment is saved into the database. This would serve to reconcile against the bill from the shipping vendor, or could serve as confirmation back to the original requester that the shipment did indeed go out.
Because we're using ODBC compliant databases, reporting against these tables should be very straightforward using tools like Crystal Report writer or Microsoft Excel.
Here's a look at what the final product needs to look like for the originator of the request. Several different fields and controls have been added, and some "removed" from this form.
Specifically...
Just a note about making "3-D" text fields -- to give a field the appearance of a three-dimensional field do the following:
1. Add a 3-D panel to your form, slightly larger than the text field you will be adding.
2. Click on the panel.
3. Add a text box to the panel.
4. Click on the panel and press F4 for the properties window.
5. For the AutoSize property, set the value to "AutoSize Child to Panel."
6. Set BevelWidth and BorderWidth to 1.
7. If you now size the panel, the text box will automatically re-size to the new size and/or shape.
In addition, there are several different fields off the visible portion of the canvas. These fields are used to store and transfer information for our program to use, not for the user to have to deal with. These fields are listed below.
To create these controls, complete the steps outlined below.
The controls are still available and these fields will still get transferred with the mail information package, as they reside on the canvas and have tagged values. This is just an easy way to pass program variables between the forms and to the different stages of your application.
From the flow of the application, it's apparent that you will be routing the form based on certain operational criteria gathered along the way. Going back to the database layout, you'll see that the Approval table contains the ID's of the pertinent users of the system. If the user's ID matches the user's authorization ID, then the user can authorize his own shipments in all cases.
The application looks at the type of shipment to determine whether the shipment must be approved. If the first 4 characters of the description of the shipping method are "APP," then the application knows that the shipment method generally requires authorization.
While there are certainly more elegant ways of doing this, for our examples here, we'll just be examining the text property of the user's choice.
At the end of the process, the shipment is either declined and the shipping request returned to the original user, or it is approved and shipped, with the specifics about the shipment recorded in the database.
The Read form takes on three different looks. On this form, we've again modified it to function as we need. We've changed the caption on the buttons and placed all the controls slightly lower on the form to make room for the information about the originator.
Note that for each of the controls on this form, the Tag property is set to correspond with the control on the Compose form. Again, as the form data packet is unpacked, the system uses the tag value to determine where the information should be placed on the form.
An important note here, if you're transferring information from a list box, as we are here, you'll need to populate the list box with the choices prior to the unpacking of the mail message. This is necessary because if your application tries to put the user's choice in the list box, it must match the available options in the list box. If it does not, the initial load of the form will fail and you'll spend quite a lot of time chasing down the "bug."
Both the Approve and Decline buttons function in a similar manner. They package up the message and send it off to the destination, either the shipping coordinator if approved, or the originator of the message if declined.
If the request is declined, the heading text on the button-bar is changed to indicate the declined status. Also, the way we pass the decline status back and forth is with the cbDeclined control, placed on the canvas off to the side and marked invisible.
If the request is approved, when the form is read, the heading message is also changed to indicate the approval. In addition, we turn on the "Complete Shipment" button, allowing the updates to the database.
If the user chooses the "Complete Shipment" option, this is just a standard form that the application calls up to enable the database updates. The only significant coding behind this screen is the database update, behind the "Save" button.
The fields are pretty much derived from the main screen and just copied into this screen to make it easier to complete.
This screen completes the cycle for the application.
As we load the form, we've loaded the current user into the gUser.Name property, and the shipping coordinator gets set to be the default recipient of the message. The 0-indexed location of the gRecipients array contains the first user that is a recipient of the message. Note that we don't need to use the MEF library calls to add a user as we don't care about populating the lstToRecipients list box because it's turned off for our application.
Set the type of recipient and call MAPIResolveName to make sure the name is unique. Again, if it's not, the user will be presented with a dialog box to choose a specific destination user. MAPIResolveName will automatically update our gRecipients control with the value of the resolved name.
'add the primary recipient as the shipping id gmessage.recipcount = 1 ReDim grecipients(0) As MapiRecip grecipients(0).name = tset("shippingID") grecipients(0).RecipClass = mapi_to Dim cancel As Integer cancel = MAPIResolveName(gSession, 0, grecipients(0).name, 0, 0, grecipients(0))
Next, we use some of the "hidden" controls, located off the visible screen, to the right.
shippingdest.Text = tset("ShippingID") approveby.Text = tset("ApprovalID") approval.Text = tset("ApprovalID")
It's easiest to establish these values now so we can close the database connection, and so that we can transfer these values to the Read form when we get to that point.
Next, we'll examine the Send button functionality.
We've added the following statement to the procedure:
If cbappreq.Value = False Then approveby.Text = "Approved"
First, we're checking to see if approval is required for this request. Our later test is based on the value of Approveby.Text -- if it's "Approved" then we can go ahead and send to shipping.
Later in the procedure is the test to determine where the message will actually be sent. The following code example shows this test.
If cbappreq.Value = True Then gmessage.recipcount = 1 ReDim grecipients(0) As MapiRecip grecipients(0).name = approveby.Text grecipients(0).RecipClass = mapi_to 'if you want to debug the address to which you are 'sending this message, un-comment the following line: 'mefdolistdetails grecipients(), gmessage.recipcount, mapi_to, 0 Dim cancel As Integer cancel = MAPIResolveName(gSession, 0, grecipients(0).name, 0, 0, grecipients(0)) If cancel <> SUCCESS_SUCCESS Then MsgBox ("The authorization address for this message is not valid.") End End If End If 'If lstToRecipients or lstCCRecipients are 'removed from the form 'then replace them with 'Nothing' in the next statement. If MEFDoResolveNames(grecipients(), gmessage.recipcount, gModified, lstToRecipients, lstCCRecipients, False) = False Then Exit Sub End If
First, check to see if the name needs to be changed. If it does, we reinitialize the gReceipients control and put the name in as appropriate. The name comes from the hidden controls set up during the Form Load event.
If you just change gRecipients(0) to the name of the user you want to send to, it won't go out to them if you've previously called MEFDoResolveNames for a recipient. This is because the actual addressing information, for example, the postoffice name, mail box name, and so forth, are all established and are what the mail message uses as the destination user address. If you change the gRecipients() item at that highest level without the rest of the updates, MAPI will ignore the "friendly name" you specify and use the resolved address information provided by the MEFDoResolveNames procedure already completed.
The MEFDoListDetails item, currently commented out, will be very helpful in determining where your form is headed. If you're debugging and can't figure out why the form is routed the way it is, un-comment that line and run the application. It will put up a dialog with the name fully resolved. This will help you track down errors like failing to ReDim the grecipients controls.
Always call MAPIResolveName to verify that the name you've entered is unique. This allows you to control the resolution, as compared to hoping that mail will find the correct destination for your message.
There are some unique circumstances in the Read form that force the customization of several different things. Specifically, if the form is returned to the user or sent on to shipping, different things are needed. First, if the form is declined and returned to the user, it should be changed when the user receives it to indicate that it was declined.
Second, if the form is approved and sent to shipping, the form should allow for the completion of the transaction and logging the database information as needed. With this in mind, the following modifications were made to what would be a relatively "standard" Read form (these are in addition, of course, to pasting the correct controls from the Compose form and setting up the format of the Read form):
The standard forward and reply buttons just send a standard mail message to a new recipient. What we wanted to do was to package up the form, ship it off, and re-display it at the recipient.
The easiest way to accomplish this was to just copy in the "cmdSend_Click" procedure, as that's really what we want to accomplish. The only modifications to the procedure were to set the recipients as noted above and to change the captions on the button from "Forward" to "Accept" and from "Reply" to "Decline."
By using the Send logic, we're forcing the form to package itself up in the standard way and ship itself off to the recipient. This keeps us from having to re-create the send logic just because we want to make some modifications.
The Form Load event contains the same combo-box loading routines for the state list box and for the shipping method box. This is necessary for the un-packaging routines to function correctly as they must be able to put a value into the control that exists among the various available choices.
In addition, the Read form may be read by any of three people. First, the authorization person may be reading it to accept or decline it. Second, the original user may be receiving back a declined request. Third, the shipping coordinator may be receiving it, ready to ship the requested item.
For these reasons, we also have to do some housekeeping in the Load event.
Specifically, we manage the "Complete Shipment" button and the header bar message. These items indicate to the user exactly what is expected of them when they receive the message.
This button shows a separate, "non-eform" form. The form prompts the user for basic information about the shipment. This information is largely pulled from the user's request, but also includes information about the actual shipping method used, and any applicable billing information.
After the user enters and/or confirms this information, they press the Save button and the information is written to the database.
The code to pull the information from the form is outlined below.
'connect to the database, open the table and record 'the shipment. Set tdb = OpenDatabase("c:\windows\teched.mdb") Set ttable = tdb.OpenTable("Shipments") ttable.AddNew ttable("Address1") = Left$(txtAddr1.Text, 100) ttable("Address2") = Left$(txtAddr2.Text, 100) ttable("city") = Left$(txtCity.Text, 40) ttable("state") = Left$(txtstate.Text, 2) ttable("Zipcode") = Left$(txtZipCode.Text, 10) ttable("requestor") = Left$(txtOriginator.Text, 50) ttable("Shippedby") = Left$(guser.name, 50) ttable("dateshipped") = Format$(Now, "mm/dd/yy") ttable("timeshipped") = Format$(Now, "hh:mm") ttable("DateOrigRequest") = Left$(frmRead.txtDateReceived.Text, 8) ttable("ShipMethod") = Left$(txtshipmethod.Text, 50) ttable("BillingRef") = Left$(txtbillingreference.Text, 50) ttable("comments") = txtNote.Text ttable("PackageID") = Val(pkgID.Text) ttable.Update ttable.Close tdb.Close
This code just pastes the information into the new record in the table and saves it. The update is performed against the Shipments table.
There are several tables that support the shipping request application. These tables, residing in TechED.MDB, provide the information used to authenticate a user on the system, information about the next step in the routing process, and also store information about the package.
Because the tables are the source of this information, we can change the routing of an eform dynamically as it moves around the mail network. This is done by modifying the tables; no modification to the program code is needed.
The approval table will actually dictate the routing for the form. The user ID corresponds to the user that is creating, or originating, the shipping request. We first validate the user by using their sign-in ID to lookup UserID in this table. The corresponding values for ApprovalID and ShippingID are then used in the application to set up the routing.
Approval Database Table
Column Type Field Size UserID Text 50 ApprovalID Text 50 ShippingID Text 50
In cases where ApprovalID = UserID, the user is allowed to "authorize" his own shipments and the request is forwarded directly to ShippingID for processing. If the ApprovalID does not match the UserID, and the shipment requires approval, the request will first be routed to the ApprovalID user, then it will either be declined and returned to the originator, or approved and forwarded to ShippingID.
PackageID Database Table
Column Type Field Size ID Double N/A
The package ID is simply a counter that will return the last used PackageID. We add one to this value and save it back to the database, using the new value as our package ID.
Shipments Database Table
Column Type Field Size Address1 Text 100 Address2 Text 100 City Text 40 State Text 2 Zipcode Text 10 Requestor Text 50 ShippedBy Text 50 DateShipped Text 8 TimeShipped Text 10 DateOrigReques Text 8 t ShipMethod Text 50 ApproveBy Text 50 BillingRef Text 100 Comments Memo N/A
The Shipments table will track the actual packages that get approved and shipped. Once the shipment is completed by the ShippingID operator, the information about the shipment is saved into the database. This would serve to reconcile against the bill from the shipping vendor, or could serve as confirmation back to the original requester that the shipment did indeed go out.
As you probably know, with Visual Basic you can create bound controls -- controls that are automatically tied to the database. We've not used these here because it's easier to control your database tables in this type of application on a single transaction basis. If the application provided more browsing types of capabilities, or something that required more database access than what we're doing, bound data controls would be a great thing to implement.
For our uses, we've gone the more manual route of creating database connections and dynasets that relate to those databases. To make these types of connections, you need to make the connection to the database and tables as follows:
... Dim pid As Double Dim tDB as Database Dim tTable as Table Set tdb = OpenDatabase("c:\windows\teched.mdb") Set ttable = tdb.OpenTable("PackageID") If Not ttable.EOF Then 'if not empty ttable.Edit pid = ttable.Fields("ID") pid = pid + 1 ttable.Fields("ID") = pid ttable.Update Else 'otherwise, insert the first one ttable.AddNew ttable.Fields("ID") = 1 pid = 1 ttable.Update End If ...
This opens the database "C:\WINDOWS\TECHED.MDB" and sets up the table "PackageID" for our use. This is actually done in the Load event associated with the Compose form. This database contains the last package ID, so we read the ID, add one to it, and save this new value. This value becomes the package ID for the new package shipping request. We do much the same later in the Load event in going out and getting the shipping ID and authorization ID using the current user's login name.
If you share a database, make sure you set up the database for shared access. In Microsoft Access, you must enable the sharing on the database, or, depending on your system configuration, the database may be flagged as single user.
Make sure you place the database somewhere where the users have read-write access to it (if applicable).
There may be a situation where a user only needs access to a certain table in a given database. If the remainder of the database needs to be locked down for security reasons, consider using a combination of local and shared databases. You should break apart the secure and non-secure database tables to further your security efforts.
If you are going to implement a shared database, it's best to pull the path out of the users' MSMAIL.INI files, or out of some other configuration information you maintain for their system (a custom INI file for example). This is because, in many cases, various users' systems on your network will not have identical set ups. If you count on a specific drive, share name, or some other such resource as your only source of shared data, you may have trouble getting to it.
Microsoft has created a subdirectory that is "sacred" within the shared file system mail subdirectory structure. This subdirectory, within the various subdirectories of the mail system, is "WGDB." By making it sacred, Microsoft has indicated that they do not plan to use or implement any software that modifies this subdirectory or its contents. This is important as it gives you a consistent place to put your applications, SHARED.INI files, and so forth, that you can count on in each postoffice environment. This subdirectory will be located immediately off the root share for the mail data files, or the "ServerPath" setting in the user's MSMAIL.INI file.
Using this subdirectory, if you retrieve the SharedExtensionsDir from the users INI file and it returns "M:\MYSHARE" you can just add "\WGDB" and you'll have the location of the workgroup applications on your system. Notice that you can, of course, add application-specific subdirectories under the WGDB subdirectory to organize your implementations.
In some cases, you'll want to have a combination of databases for the application. This is often the case where you allow the creator of a request to create a form, but want to log the form, or its contents, in a database on the recipient's end.
This is convenient where you may have users on physically removed systems. In other words, if you have remote users, put the information they need for the creation of a form in their local postoffice or on their local hard drive. When the request is received at the recipient, presumably at a remote location relative to the originator, the Read form can save the information as necessary to a local database.
Routing is probably the most appealing portion of workgroup applications. In addition, it's one of the more challenging aspects of automating an application. Not only must you provide for the mainstream routing -- those things that happen as a rule -- but also the exception-based routing.
In our example, we've included both of these instances with a couple of twists. There are several different factors that impact the routing of our form. Reviewing the original flow chart, we can see the tests that are considered when determining the routing.
At the "Authorization Required" step, the form is examined for several things. First, if the shipping method is not one that requires authorization, we send the message immediately to the shipping coordinator. The way we know if the shipping method requires approval is by examining the actual title of the shipping method. If the first portion of the shipping method is "APP:" then we know it must generally be approved.
Second, if the shipment must be approved, then we must check on our user. The user may be authorized to approve his own shipments. If the ApprovalID retrieved from the database is the same as this user's ID, then he is able to approve his own shipments and the shipment is sent off to the shipping coordinator that we've retrieved from the database.
Third, if the shipment must be approved by another user, we make that user the recipient of the message and send off the message for approval. We'll detail this process of retrieving the database information and using it to address the forms next.
The database table that we will be working with in this portion of the application is the Approval table. To access the table, we first need to know who "we" are. Remember, the goal is to use the logon process already provided by mail and potentially Microsoft Windows® for Workgroups and/or Windows NT. The current user's ID is stored automatically for us in gUser.Name. This is the "friendly" name of the user, that is the user name that you are used to seeing in the address book.
A quick note here about the user name. If you're working in a situation where you are using your mail client software off-line (perhaps in a remote situation) and are using the gUser.Name to determine the identity of the user, it is different off-line than on-line. For example, for my user ID, on-line it's "Steve Wynkoop" but off-line it's "SteveW [Working Off-line]" -- an important fact to keep in mind.
Next, we want to look up the name in the database to determine:
Here's a look at the code that accomplishes this:
'get the destination of the form, assuming no further 'authorization is required. Set tdb = OpenDatabase("c:\windows\teched.mdb") Dim tset As dynaset Set tset = tdb.CreateDynaset("approval") tset.FindFirst "userid = '" & guser.name & "'" If Not tset.NoMatch Then 'add the primary recipient as the shipping id gmessage.recipcount = 1 ReDim grecipients(0) As MapiRecip grecipients(0).name = tset("shippingID") grecipients(0).RecipClass = mapi_to Dim cancel As Integer cancel = MAPIResolveName(gSession, 0, grecipients(0).name, 0, 0, grecipients(0)) If cancel <> SUCCESS_SUCCESS Then MsgBox ("The default shipping coordinator address, '" & tset("ShippingID") & " could not be resolved. It may not exist in your default address book, or it may not be unique in your address book.") End If shippingdest.Text = tset("ShippingID") approveby.Text = tset("ApprovalID") approval.Text = tset("ApprovalID") Else 'the user is not registered in the database, 'get out MsgBox ("Your user ID, '" & guser.name & "', is not registered to use the shipping form.") End End If 'set up the authorization field If guser.name = approveby.Text Then approveby.Text = "Approved" Exit Sub 'if so, exit this sub End If
There are several items to note here. First of all, we're enforcing a level of security in checking that the user name does exist in the database. If it doesn't, we end the program after indicating to the user what the problem is.
It's also important in administrative routing applications that you're sure you have unique user names assigned to the routing. If the name is not unique in the user's address book, either the user will be asked to clarify the address with a dialog box listing all the possibilities, or the form will fail when it is sent. Either way, the desired effect is obviously not attained.
Another important note is that the names provided in the mail message are resolved against the user's local address book, and they are checked against whatever the user has picked as his default address book, be it the Personal Address Book, the Global Address List, or the Post Office Address Book. This may, or may not, affect your application's ability to look up a given name.
Testing is one of the more challenging aspects of developing workgroup applications, especially where the application behaves differently depending on values and/or circumstances encountered as it is used.
First of all, you'll want to create a test user in your mail system. This user should be set up to be the recipient of your messages if you're testing routing. If you're testing forms that are routed by the user selecting the recipient, you can test the system by just sending messages to yourself.
From within Visual Basic you can create new messages, but you can't activate the Read form easily. To test the system, first create a new message using your standard Compose form. Next, go into mail, signed in as the user that would be the recipient of the message.
If you double-click on the new message, it should call up your new application in the Read form mode. The code in the Read form Load event should execute and, of course, you should get no error messages.
Typically, the error messages will indicate that there was some trouble un-packing information from the Compose form to be placed in the Read form. In the sample code, I've placed some debugging message boxes in places where you'll probably be having the most difficulties. Specifically, in the ReadTaggedControls procedure in EFORM.BAS, there is a loop that is especially helpful to have message boxes report back on -- the main loop is the loop responsible for the movement of the information from the mail system to your form.
Sub ReadTaggedControls (frm As Form, ByVal package As Long) Dim i As Integer Dim Tag As String Dim ctrl As Control Dim Index As Long For i = 0 To frm.Controls.Count - 1 Set ctrl = frm.Controls(i) If ctrl.Tag <> "" Then Tag = ctrl.Tag 'for debug, turn on the following statement 'MsgBox ("Undoing " & ctrl.Tag) 'Native controls. ...
The "Undoing" message box will let you know what controls are being worked on just before you would generally receive any messages regarding errors. Remember, if you're loading values into a combo box, the value you're trying to set in the Text property must exist in the combo box list of values. In our example, this is taken care of in the Form Load event. In controls you are loading, you'll need to make sure that the Visual Basic rules are met, in addition to the rules of the Electronic Forms Designer API.
There are several sources of information that can be used to supplement this paper; some of these are listed here.
Microsoft Electronic Forms Designer manuals and documentation, on-line help. This comes with the Electronic Forms Designer, so be sure to take advantage of the on-line documentation, including the guide to MAPI and the Electronic Forms Designer API capabilities.
Microsoft Workgroup Templates. This package provides 10 ready to use and modify templates that show off the different types of routing (administrative and ad-hoc) and several applications, ranging from Travel Request forms to Help Desk automation applications. Database access is outlined, as are some possible future directions for the sample templates. Full source code is provided for each of these applications.
Experiment, play, enjoy. The best way to familiarize yourself with the capabilities and possibilities of the system is to dive in, pick an application and do it. I wish you luck and fun in your endeavors into this new and exciting territory.
© 1995 Microsoft Corporation.
THESE MATERIALS ARE PROVIDED "AS-IS," FOR INFORMATIONAL
PURPOSES ONLY.
NEITHER MICROSOFT NOR ITS SUPPLIERS MAKES ANY WARRANTY, EXPRESS
OR IMPLIED WITH RESPECT TO THE CONTENT OF THESE MATERIALS OR THE
ACCURACY OF ANY INFORMATION CONTAINED HEREIN, INCLUDING, WITHOUT
LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS
FOR A PARTICULAR PURPOSE. BECAUSE SOME STATES/JURISDICTIONS DO
NOT ALLOW EXCLUSIONS OF IMPLIED WARRANTIES, THE ABOVE LIMITATION
MAY NOT APPLY TO YOU.
NEITHER MICROSOFT NOR ITS SUPPLIERS SHALL HAVE ANY LIABILITY FOR
ANY DAMAGES WHATSOEVER INCLUDING CONSEQUENTIAL INCIDENTAL, DIRECT,
INDIRECT, SPECIAL, AND LOSS PROFITS. BECAUSE SOME STATES/JURISDICTIONS
DO NOT ALLOW THE EXCLUSION OF CONSEQUENTIAL OR INCIDENTAL DAMAGES,
THE ABOVE LIMITATION MAY NOT APPLY TO YOU. IN ANY EVENT, MICROSOFT'S
AND ITS SUPPLIERS' ENTIRE LIABILITY IN ANY MANNER ARISING OUT
OF THESE MATERIALS, WHETHER BY TORT, CONTRACT, OR OTHERWISE SHALL
NOT EXCEED THE SUGGESTED RETAIL PRICE OF THESE MATERIALS.
![]() |
Click Here to Search TechNet Web Contents | TechNet CD Overview | Microsoft TechNet Credit Card Order Form At this time we can only support electronic orders in the US and Canada. International ordering information. |
©1996 Microsoft Corporation |